Clean up more ActivityPub code, refactoring

This commit is contained in:
Jesse Wierzbinski 2023-10-22 19:39:42 -10:00
parent d05b077df1
commit 80a3e4c92d
No known key found for this signature in database
GPG key ID: F9A1E418934E40B0
26 changed files with 317 additions and 170 deletions

View file

@ -6,13 +6,14 @@ import {
PrimaryGeneratedColumn, PrimaryGeneratedColumn,
} from "typeorm"; } from "typeorm";
import { APImage, APObject, DateTime } from "activitypub-types"; import { APImage, APObject, DateTime } from "activitypub-types";
import { getConfig } from "@config"; import { ConfigType, getConfig } from "@config";
import { appendFile } from "fs/promises"; import { appendFile } from "fs/promises";
import { APIStatus } from "~types/entities/status"; import { APIStatus } from "~types/entities/status";
import { RawActor } from "./RawActor"; import { RawActor } from "./RawActor";
import { APIAccount } from "~types/entities/account"; import { APIAccount } from "~types/entities/account";
import { APIEmoji } from "~types/entities/emoji"; import { APIEmoji } from "~types/entities/emoji";
import { User } from "./User"; import { User } from "./User";
import { Status } from "./Status";
/** /**
* Represents a raw ActivityPub object in the database. * Represents a raw ActivityPub object in the database.
@ -171,4 +172,57 @@ export class RawObject extends BaseEntity {
static async exists(id: string) { static async exists(id: string) {
return !!(await RawObject.getById(id)); return !!(await RawObject.getById(id));
} }
/**
* Creates a RawObject instance from a Status object.
* DOES NOT SAVE THE OBJECT TO THE DATABASE.
* @param status The Status object to create the RawObject from.
* @returns A Promise that resolves to the RawObject instance.
*/
static createFromStatus(status: Status, config: ConfigType) {
const object = new RawObject();
object.data = {
id: `${config.http.base_url}/users/${status.account.username}/statuses/${status.id}`,
type: "Note",
summary: status.spoiler_text,
content: status.content,
inReplyTo: status.in_reply_to_post?.object.data.id,
published: new Date().toISOString(),
tag: [],
attributedTo: `${config.http.base_url}/users/${status.account.username}`,
};
// Map status mentions to ActivityPub Actor IDs
const mentionedUsers = status.mentions.map(
user => user.actor.data.id as string
);
object.data.to = mentionedUsers;
if (status.visibility === "private") {
object.data.cc = [
`${config.http.base_url}/users/${status.account.username}/followers`,
];
} else if (status.visibility === "direct") {
// Add nothing else
} else if (status.visibility === "public") {
object.data.to = [
...object.data.to,
"https://www.w3.org/ns/activitystreams#Public",
];
object.data.cc = [
`${config.http.base_url}/users/${status.account.username}/followers`,
];
}
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
else if (status.visibility === "unlisted") {
object.data.to = [
...object.data.to,
"https://www.w3.org/ns/activitystreams#Public",
];
}
return object;
}
} }

View file

@ -12,7 +12,7 @@ import {
UpdateDateColumn, UpdateDateColumn,
} from "typeorm"; } from "typeorm";
import { APIStatus } from "~types/entities/status"; import { APIStatus } from "~types/entities/status";
import { User } from "./User"; import { User, userRelations } from "./User";
import { Application } from "./Application"; import { Application } from "./Application";
import { Emoji } from "./Emoji"; import { Emoji } from "./Emoji";
import { RawActivity } from "./RawActivity"; import { RawActivity } from "./RawActivity";
@ -27,7 +27,6 @@ export const statusRelations = [
"object", "object",
"in_reply_to_post", "in_reply_to_post",
"instance", "instance",
"in_reply_to_account",
"in_reply_to_post.account", "in_reply_to_post.account",
"application", "application",
"emojis", "emojis",
@ -36,6 +35,16 @@ export const statusRelations = [
"announces", "announces",
]; ];
export const statusAndUserRelations = [
...statusRelations,
...[
"account.actor",
"account.relationships",
"account.pinned_notes",
"account.instance",
],
];
/** /**
* Represents a status (i.e. a post) * Represents a status (i.e. a post)
*/ */
@ -122,14 +131,6 @@ export class Status extends BaseEntity {
}) })
instance!: Instance | null; instance!: Instance | null;
/**
* The raw actor that this status is a reply to, if any.
*/
@ManyToOne(() => User, {
nullable: true,
})
in_reply_to_account!: User | null;
/** /**
* Whether this status is sensitive. * Whether this status is sensitive.
*/ */
@ -343,26 +344,9 @@ export class Status extends BaseEntity {
newStatus.mentions = []; newStatus.mentions = [];
newStatus.instance = data.account.instance; newStatus.instance = data.account.instance;
newStatus.object = new RawObject();
if (data.reply) { if (data.reply) {
newStatus.in_reply_to_post = data.reply.status; newStatus.in_reply_to_post = data.reply.status;
newStatus.in_reply_to_account = data.reply.user;
} }
newStatus.object.data = {
id: `${config.http.base_url}/users/${data.account.username}/statuses/${newStatus.id}`,
type: "Note",
summary: data.spoiler_text,
content: data.content,
inReplyTo: data.reply?.status
? data.reply.status.object.data.id
: undefined,
published: new Date().toISOString(),
tag: [],
attributedTo: `${config.http.base_url}/users/${data.account.username}`,
};
// Get people mentioned in the content // Get people mentioned in the content
const mentionedPeople = [ const mentionedPeople = [
...data.content.matchAll(/@([a-zA-Z0-9_]+)/g), ...data.content.matchAll(/@([a-zA-Z0-9_]+)/g),
@ -370,79 +354,45 @@ export class Status extends BaseEntity {
return `${config.http.base_url}/users/${match[1]}`; return `${config.http.base_url}/users/${match[1]}`;
}); });
// Map this to Users // Get list of mentioned users
const mentionedUsers = ( await Promise.all(
await Promise.all( mentionedPeople.map(async person => {
mentionedPeople.map(async person => { // Check if post is in format @username or @username@instance.com
// Check if post is in format @username or @username@instance.com // If is @username, the user is a local user
// If is @username, the user is a local user const instanceUrl =
const instanceUrl = person.split("@").length === 3
person.split("@").length === 3 ? person.split("@")[2]
? person.split("@")[2] : null;
: null;
if (instanceUrl) { if (instanceUrl) {
const user = await User.findOne({ const user = await User.findOne({
where: { where: {
username: person.split("@")[1], username: person.split("@")[1],
// If contains instanceUrl // If contains instanceUrl
instance: { instance: {
base_url: instanceUrl, base_url: instanceUrl,
},
}, },
relations: { },
actor: true, relations: userRelations,
instance: true, });
},
});
newStatus.mentions.push(user as User); newStatus.mentions.push(user as User);
} else {
const user = await User.findOne({
where: {
username: person.split("@")[1],
},
relations: userRelations,
});
return user?.actor.data.id; newStatus.mentions.push(user as User);
} else { }
const user = await User.findOne({ })
where: { );
username: person.split("@")[1],
},
relations: {
actor: true,
},
});
newStatus.mentions.push(user as User); const object = RawObject.createFromStatus(newStatus, config);
return user?.actor.data.id; newStatus.object = object;
}
})
)
).map(user => user as string);
newStatus.object.data.to = mentionedUsers;
if (data.visibility === "private") {
newStatus.object.data.cc = [
`${config.http.base_url}/users/${data.account.username}/followers`,
];
} else if (data.visibility === "direct") {
// Add nothing else
} else if (data.visibility === "public") {
newStatus.object.data.to = [
...newStatus.object.data.to,
"https://www.w3.org/ns/activitystreams#Public",
];
newStatus.object.data.cc = [
`${config.http.base_url}/users/${data.account.username}/followers`,
];
}
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
else if (data.visibility === "unlisted") {
newStatus.object.data.to = [
...newStatus.object.data.to,
"https://www.w3.org/ns/activitystreams#Public",
];
}
// TODO: Add default language
await newStatus.object.save(); await newStatus.object.save();
await newStatus.save(); await newStatus.save();
return newStatus; return newStatus;
@ -453,11 +403,57 @@ export class Status extends BaseEntity {
* @returns A promise that resolves with the API status. * @returns A promise that resolves with the API status.
*/ */
async toAPI(): Promise<APIStatus> { async toAPI(): Promise<APIStatus> {
const reblogCount = await Status.count({
where: {
reblog: {
id: this.id,
},
},
relations: ["reblog"],
});
const repliesCount = await Status.count({
where: {
in_reply_to_post: {
id: this.id,
},
},
relations: ["in_reply_to_post"],
});
return { return {
...(await this.object.toAPI()),
id: this.id, id: this.id,
in_reply_to_id: this.in_reply_to_post?.id || null, in_reply_to_id: this.in_reply_to_post?.id || null,
in_reply_to_account_id: this.in_reply_to_post?.account.id || null, in_reply_to_account_id: this.in_reply_to_post?.account.id || null,
account: await this.account.toAPI(),
created_at: new Date(this.created_at).toISOString(),
application: (await this.application?.toAPI()) || null,
card: null,
content: this.content,
emojis: await Promise.all(this.emojis.map(emoji => emoji.toAPI())),
favourited: false,
favourites_count: 0,
media_attachments: [],
mentions: await Promise.all(
this.mentions.map(async m => await m.toAPI())
),
language: null,
muted: false,
pinned: this.account.pinned_notes.some(note => note.id === this.id),
poll: null,
reblog: this.reblog ? await this.reblog.toAPI() : null,
reblogged: !!this.reblog,
reblogs_count: reblogCount,
replies_count: repliesCount,
sensitive: false,
spoiler_text: "",
tags: [],
uri: `${config.http.base_url}/users/${this.account.username}/statuses/${this.id}`,
visibility: "public",
url: `${config.http.base_url}/users/${this.account.username}/statuses/${this.id}`,
bookmarked: false,
quote: null,
quote_id: undefined,
}; };
} }
} }

View file

@ -19,13 +19,19 @@ import {
APCollectionPage, APCollectionPage,
APOrderedCollectionPage, APOrderedCollectionPage,
} from "activitypub-types"; } from "activitypub-types";
import { RawObject } from "./RawObject";
import { Token } from "./Token"; import { Token } from "./Token";
import { Status, statusRelations } from "./Status"; import { Status, statusRelations } from "./Status";
import { APISource } from "~types/entities/source"; import { APISource } from "~types/entities/source";
import { Relationship } from "./Relationship"; import { Relationship } from "./Relationship";
import { Instance } from "./Instance"; import { Instance } from "./Instance";
export const userRelations = [
"actor",
"relationships",
"pinned_notes",
"instance",
];
/** /**
* Represents a user in the database. * Represents a user in the database.
* Stores local and remote users * Stores local and remote users
@ -156,9 +162,9 @@ export class User extends BaseEntity {
/** /**
* The pinned notes for the user. * The pinned notes for the user.
*/ */
@ManyToMany(() => RawObject, object => object.id) @ManyToMany(() => Status, status => status.id)
@JoinTable() @JoinTable()
pinned_notes!: RawObject[]; pinned_notes!: Status[];
/** /**
* Get the user's avatar in raw URL format * Get the user's avatar in raw URL format
@ -296,6 +302,8 @@ export class User extends BaseEntity {
fields: [], fields: [],
}; };
user.pinned_notes = [];
await user.generateKeys(); await user.generateKeys();
await user.save(); await user.save();
await user.updateActor(); await user.updateActor();
@ -315,12 +323,7 @@ export class User extends BaseEntity {
where: { where: {
access_token, access_token,
}, },
relations: { relations: userRelations.map(r => `user.${r}`),
user: {
relationships: true,
actor: true,
},
},
}); });
if (!token) return null; if (!token) return null;
@ -524,11 +527,48 @@ export class User extends BaseEntity {
relations: ["owner"], relations: ["owner"],
}); });
const statusCount = await Status.count({
where: {
account: {
id: this.id,
},
},
relations: ["account"],
});
const config = getConfig();
return { return {
...(await this.actor.toAPIAccount(isOwnAccount)),
id: this.id, id: this.id,
username: this.username,
display_name: this.display_name,
note: this.note,
url: `${config.http.base_url}/users/${this.username}`,
avatar: this.getAvatarUrl(config) || config.defaults.avatar,
header: this.getHeaderUrl(config) || config.defaults.header,
locked: false,
created_at: new Date(this.created_at).toISOString(),
followers_count: follower_count, followers_count: follower_count,
following_count: following_count, following_count: following_count,
statuses_count: statusCount,
emojis: [],
fields: [],
bot: false,
source: isOwnAccount ? this.source : undefined,
avatar_static: "",
header_static: "",
acct:
this.instance === null
? `${this.username}`
: `${this.username}@${this.instance.base_url}`,
limited: false,
moved: null,
noindex: false,
suspended: false,
discoverable: undefined,
mute_expires_at: undefined,
group: false,
role: undefined,
}; };
} }
} }

View file

@ -1,6 +1,6 @@
import { errorResponse, jsonResponse } from "@response"; import { errorResponse, jsonResponse } from "@response";
import { MatchedRoute } from "bun"; import { MatchedRoute } from "bun";
import { User } from "~database/entities/User"; import { User, userRelations } from "~database/entities/User";
import { getConfig, getHost } from "@config"; import { getConfig, getHost } from "@config";
import { applyConfig } from "@api"; import { applyConfig } from "@api";
@ -34,7 +34,10 @@ export default async (
return errorResponse("User is a remote user", 404); return errorResponse("User is a remote user", 404);
} }
const user = await User.findOneBy({ username: requestedUser.split("@")[0] }); const user = await User.findOne({
where: { username: requestedUser.split("@")[0] },
relations: userRelations
});
if (!user) { if (!user) {
return errorResponse("User not found", 404); return errorResponse("User not found", 404);

View file

@ -1,7 +1,7 @@
import { errorResponse, jsonResponse } from "@response"; import { errorResponse, jsonResponse } from "@response";
import { MatchedRoute } from "bun"; import { MatchedRoute } from "bun";
import { Relationship } from "~database/entities/Relationship"; import { Relationship } from "~database/entities/Relationship";
import { User } from "~database/entities/User"; import { User, userRelations } from "~database/entities/User";
import { applyConfig } from "@api"; import { applyConfig } from "@api";
export const meta = applyConfig({ export const meta = applyConfig({
@ -29,8 +29,11 @@ export default async (
if (!self) return errorResponse("Unauthorized", 401); if (!self) return errorResponse("Unauthorized", 401);
const user = await User.findOneBy({ const user = await User.findOne({
id, where: {
id,
},
relations: userRelations,
}); });
if (!user) return errorResponse("User not found", 404); if (!user) return errorResponse("User not found", 404);

View file

@ -2,7 +2,7 @@ import { parseRequest } from "@request";
import { errorResponse, jsonResponse } from "@response"; import { errorResponse, jsonResponse } from "@response";
import { MatchedRoute } from "bun"; import { MatchedRoute } from "bun";
import { Relationship } from "~database/entities/Relationship"; import { Relationship } from "~database/entities/Relationship";
import { User } from "~database/entities/User"; import { User, userRelations } from "~database/entities/User";
import { applyConfig } from "@api"; import { applyConfig } from "@api";
export const meta = applyConfig({ export const meta = applyConfig({
@ -36,8 +36,11 @@ export default async (
languages?: string[]; languages?: string[];
}>(req); }>(req);
const user = await User.findOneBy({ const user = await User.findOne({
id, where: {
id,
},
relations: userRelations,
}); });
if (!user) return errorResponse("User not found", 404); if (!user) return errorResponse("User not found", 404);

View file

@ -1,6 +1,6 @@
import { errorResponse, jsonResponse } from "@response"; import { errorResponse, jsonResponse } from "@response";
import { MatchedRoute } from "bun"; import { MatchedRoute } from "bun";
import { User } from "~database/entities/User"; import { User, userRelations } from "~database/entities/User";
import { applyConfig } from "@api"; import { applyConfig } from "@api";
export const meta = applyConfig({ export const meta = applyConfig({
@ -28,8 +28,11 @@ export default async (
let foundUser: User | null; let foundUser: User | null;
try { try {
foundUser = await User.findOneBy({ foundUser = await User.findOne({
id, where: {
id,
},
relations: userRelations,
}); });
} catch (e) { } catch (e) {
return errorResponse("Invalid ID", 404); return errorResponse("Invalid ID", 404);

View file

@ -2,7 +2,7 @@ import { parseRequest } from "@request";
import { errorResponse, jsonResponse } from "@response"; import { errorResponse, jsonResponse } from "@response";
import { MatchedRoute } from "bun"; import { MatchedRoute } from "bun";
import { Relationship } from "~database/entities/Relationship"; import { Relationship } from "~database/entities/Relationship";
import { User } from "~database/entities/User"; import { User, userRelations } from "~database/entities/User";
import { applyConfig } from "@api"; import { applyConfig } from "@api";
export const meta = applyConfig({ export const meta = applyConfig({
@ -36,8 +36,11 @@ export default async (
duration: number; duration: number;
}>(req); }>(req);
const user = await User.findOneBy({ const user = await User.findOne({
id, where: {
id,
},
relations: userRelations,
}); });
if (!user) return errorResponse("User not found", 404); if (!user) return errorResponse("User not found", 404);

View file

@ -2,7 +2,7 @@ import { parseRequest } from "@request";
import { errorResponse, jsonResponse } from "@response"; import { errorResponse, jsonResponse } from "@response";
import { MatchedRoute } from "bun"; import { MatchedRoute } from "bun";
import { Relationship } from "~database/entities/Relationship"; import { Relationship } from "~database/entities/Relationship";
import { User } from "~database/entities/User"; import { User, userRelations } from "~database/entities/User";
import { applyConfig } from "@api"; import { applyConfig } from "@api";
export const meta = applyConfig({ export const meta = applyConfig({
@ -34,8 +34,11 @@ export default async (
comment: string; comment: string;
}>(req); }>(req);
const user = await User.findOneBy({ const user = await User.findOne({
id, where: {
id,
},
relations: userRelations,
}); });
if (!user) return errorResponse("User not found", 404); if (!user) return errorResponse("User not found", 404);

View file

@ -1,7 +1,7 @@
import { errorResponse, jsonResponse } from "@response"; import { errorResponse, jsonResponse } from "@response";
import { MatchedRoute } from "bun"; import { MatchedRoute } from "bun";
import { Relationship } from "~database/entities/Relationship"; import { Relationship } from "~database/entities/Relationship";
import { User } from "~database/entities/User"; import { User, userRelations } from "~database/entities/User";
import { applyConfig } from "@api"; import { applyConfig } from "@api";
export const meta = applyConfig({ export const meta = applyConfig({
@ -29,8 +29,11 @@ export default async (
if (!self) return errorResponse("Unauthorized", 401); if (!self) return errorResponse("Unauthorized", 401);
const user = await User.findOneBy({ const user = await User.findOne({
id, where: {
id,
},
relations: userRelations,
}); });
if (!user) return errorResponse("User not found", 404); if (!user) return errorResponse("User not found", 404);

View file

@ -1,7 +1,7 @@
import { errorResponse, jsonResponse } from "@response"; import { errorResponse, jsonResponse } from "@response";
import { MatchedRoute } from "bun"; import { MatchedRoute } from "bun";
import { Relationship } from "~database/entities/Relationship"; import { Relationship } from "~database/entities/Relationship";
import { User } from "~database/entities/User"; import { User, userRelations } from "~database/entities/User";
import { applyConfig } from "@api"; import { applyConfig } from "@api";
export const meta = applyConfig({ export const meta = applyConfig({
@ -29,8 +29,11 @@ export default async (
if (!self) return errorResponse("Unauthorized", 401); if (!self) return errorResponse("Unauthorized", 401);
const user = await User.findOneBy({ const user = await User.findOne({
id, where: {
id,
},
relations: userRelations,
}); });
if (!user) return errorResponse("User not found", 404); if (!user) return errorResponse("User not found", 404);

View file

@ -1,7 +1,7 @@
import { errorResponse, jsonResponse } from "@response"; import { errorResponse, jsonResponse } from "@response";
import { MatchedRoute } from "bun"; import { MatchedRoute } from "bun";
import { Status, statusRelations } from "~database/entities/Status"; import { Status, statusAndUserRelations } from "~database/entities/Status";
import { User } from "~database/entities/User"; import { User, userRelations } from "~database/entities/User";
import { applyConfig } from "@api"; import { applyConfig } from "@api";
export const meta = applyConfig({ export const meta = applyConfig({
@ -42,8 +42,11 @@ export default async (
tagged?: string; tagged?: string;
} = matchedRoute.query; } = matchedRoute.query;
const user = await User.findOneBy({ const user = await User.findOne({
id, where: {
id,
},
relations: userRelations,
}); });
if (!user) return errorResponse("User not found", 404); if (!user) return errorResponse("User not found", 404);
@ -60,7 +63,7 @@ export default async (
}, },
isReblog: exclude_reblogs ? true : undefined, isReblog: exclude_reblogs ? true : undefined,
}, },
relations: statusRelations, relations: statusAndUserRelations,
order: { order: {
created_at: "DESC", created_at: "DESC",
}, },

View file

@ -1,7 +1,7 @@
import { errorResponse, jsonResponse } from "@response"; import { errorResponse, jsonResponse } from "@response";
import { MatchedRoute } from "bun"; import { MatchedRoute } from "bun";
import { Relationship } from "~database/entities/Relationship"; import { Relationship } from "~database/entities/Relationship";
import { User } from "~database/entities/User"; import { User, userRelations } from "~database/entities/User";
import { applyConfig } from "@api"; import { applyConfig } from "@api";
export const meta = applyConfig({ export const meta = applyConfig({
@ -29,8 +29,11 @@ export default async (
if (!self) return errorResponse("Unauthorized", 401); if (!self) return errorResponse("Unauthorized", 401);
const user = await User.findOneBy({ const user = await User.findOne({
id, where: {
id,
},
relations: userRelations,
}); });
if (!user) return errorResponse("User not found", 404); if (!user) return errorResponse("User not found", 404);

View file

@ -1,7 +1,7 @@
import { errorResponse, jsonResponse } from "@response"; import { errorResponse, jsonResponse } from "@response";
import { MatchedRoute } from "bun"; import { MatchedRoute } from "bun";
import { Relationship } from "~database/entities/Relationship"; import { Relationship } from "~database/entities/Relationship";
import { User } from "~database/entities/User"; import { User, userRelations } from "~database/entities/User";
import { applyConfig } from "@api"; import { applyConfig } from "@api";
export const meta = applyConfig({ export const meta = applyConfig({
@ -29,8 +29,11 @@ export default async (
if (!self) return errorResponse("Unauthorized", 401); if (!self) return errorResponse("Unauthorized", 401);
const user = await User.findOneBy({ const user = await User.findOne({
id, where: {
id,
},
relations: userRelations,
}); });
if (!user) return errorResponse("User not found", 404); if (!user) return errorResponse("User not found", 404);

View file

@ -1,7 +1,7 @@
import { errorResponse, jsonResponse } from "@response"; import { errorResponse, jsonResponse } from "@response";
import { MatchedRoute } from "bun"; import { MatchedRoute } from "bun";
import { Relationship } from "~database/entities/Relationship"; import { Relationship } from "~database/entities/Relationship";
import { User } from "~database/entities/User"; import { User, userRelations } from "~database/entities/User";
import { applyConfig } from "@api"; import { applyConfig } from "@api";
export const meta = applyConfig({ export const meta = applyConfig({
@ -29,8 +29,11 @@ export default async (
if (!self) return errorResponse("Unauthorized", 401); if (!self) return errorResponse("Unauthorized", 401);
const user = await User.findOneBy({ const user = await User.findOne({
id, where: {
id,
},
relations: userRelations,
}); });
if (!user) return errorResponse("User not found", 404); if (!user) return errorResponse("User not found", 404);

View file

@ -1,7 +1,7 @@
import { errorResponse, jsonResponse } from "@response"; import { errorResponse, jsonResponse } from "@response";
import { MatchedRoute } from "bun"; import { MatchedRoute } from "bun";
import { Relationship } from "~database/entities/Relationship"; import { Relationship } from "~database/entities/Relationship";
import { User } from "~database/entities/User"; import { User, userRelations } from "~database/entities/User";
import { applyConfig } from "@api"; import { applyConfig } from "@api";
export const meta = applyConfig({ export const meta = applyConfig({
@ -29,8 +29,11 @@ export default async (
if (!self) return errorResponse("Unauthorized", 401); if (!self) return errorResponse("Unauthorized", 401);
const user = await User.findOneBy({ const user = await User.findOne({
id, where: {
id,
},
relations: userRelations,
}); });
if (!user) return errorResponse("User not found", 404); if (!user) return errorResponse("User not found", 404);

View file

@ -1,7 +1,7 @@
import { applyConfig } from "@api"; import { applyConfig } from "@api";
import { errorResponse, jsonResponse } from "@response"; import { errorResponse, jsonResponse } from "@response";
import { MatchedRoute } from "bun"; import { MatchedRoute } from "bun";
import { Status, statusRelations } from "~database/entities/Status"; import { Status, statusAndUserRelations } from "~database/entities/Status";
import { User } from "~database/entities/User"; import { User } from "~database/entities/User";
import { APIRouteMeta } from "~types/api"; import { APIRouteMeta } from "~types/api";
@ -35,7 +35,7 @@ export default async (
where: { where: {
id, id,
}, },
relations: statusRelations, relations: statusAndUserRelations,
}); });
} catch (e) { } catch (e) {
return errorResponse("Invalid ID", 404); return errorResponse("Invalid ID", 404);

View file

@ -3,7 +3,7 @@ import { applyConfig } from "@api";
import { parseRequest } from "@request"; import { parseRequest } from "@request";
import { errorResponse, jsonResponse } from "@response"; import { errorResponse, jsonResponse } from "@response";
import { FindManyOptions } from "typeorm"; import { FindManyOptions } from "typeorm";
import { Status, statusRelations } from "~database/entities/Status"; import { Status, statusAndUserRelations } from "~database/entities/Status";
import { User } from "~database/entities/User"; import { User } from "~database/entities/User";
import { APIRouteMeta } from "~types/api"; import { APIRouteMeta } from "~types/api";
@ -60,7 +60,7 @@ export default async (req: Request): Promise<Response> => {
created_at: "DESC", created_at: "DESC",
}, },
take: limit, take: limit,
relations: statusRelations, relations: statusAndUserRelations,
}; };
if (max_id) { if (max_id) {

View file

@ -3,7 +3,7 @@ import { applyConfig } from "@api";
import { parseRequest } from "@request"; import { parseRequest } from "@request";
import { errorResponse, jsonResponse } from "@response"; import { errorResponse, jsonResponse } from "@response";
import { FindManyOptions, IsNull, Not } from "typeorm"; import { FindManyOptions, IsNull, Not } from "typeorm";
import { Status, statusRelations } from "~database/entities/Status"; import { Status, statusAndUserRelations } from "~database/entities/Status";
import { APIRouteMeta } from "~types/api"; import { APIRouteMeta } from "~types/api";
export const meta: APIRouteMeta = applyConfig({ export const meta: APIRouteMeta = applyConfig({
@ -56,7 +56,7 @@ export default async (req: Request): Promise<Response> => {
created_at: "DESC", created_at: "DESC",
}, },
take: limit, take: limit,
relations: statusRelations, relations: statusAndUserRelations,
}; };
if (max_id) { if (max_id) {

View file

@ -4,7 +4,7 @@ import { MatchedRoute } from "bun";
import { randomBytes } from "crypto"; import { randomBytes } from "crypto";
import { Application } from "~database/entities/Application"; import { Application } from "~database/entities/Application";
import { Token } from "~database/entities/Token"; import { Token } from "~database/entities/Token";
import { User } from "~database/entities/User"; import { User, userRelations } from "~database/entities/User";
import { APIRouteMeta } from "~types/api"; import { APIRouteMeta } from "~types/api";
export const meta: APIRouteMeta = applyConfig({ export const meta: APIRouteMeta = applyConfig({
@ -45,8 +45,11 @@ export default async (
return errorResponse("Missing username or password", 400); return errorResponse("Missing username or password", 400);
// Get user // Get user
const user = await User.findOneBy({ const user = await User.findOne({
email, where: {
email,
},
relations: userRelations,
}); });
if (!user || !(await Bun.password.verify(password, user.password || ""))) if (!user || !(await Bun.password.verify(password, user.password || "")))
@ -70,5 +73,5 @@ export default async (
await token.save(); await token.save();
// Redirect back to application // Redirect back to application
return Response.redirect(`${redirect_uri}?code=${token.code}`); return Response.redirect(`${redirect_uri}?code=${token.code}`, 302);
}; };

View file

@ -1,6 +1,6 @@
import { errorResponse, jsonLdResponse } from "@response"; import { errorResponse, jsonLdResponse } from "@response";
import { MatchedRoute } from "bun"; import { MatchedRoute } from "bun";
import { User } from "~database/entities/User"; import { User, userRelations } from "~database/entities/User";
import { getConfig, getHost } from "@config"; import { getConfig, getHost } from "@config";
import { applyConfig } from "@api"; import { applyConfig } from "@api";
@ -34,7 +34,10 @@ export default async (
const username = matchedRoute.params.username; const username = matchedRoute.params.username;
const user = await User.findOneBy({ username }); const user = await User.findOne({
where: { username },
relations: userRelations,
});
if (!user) { if (!user) {
return errorResponse("User not found", 404); return errorResponse("User not found", 404);

View file

@ -1,6 +1,6 @@
import { errorResponse, jsonLdResponse } from "@response"; import { errorResponse, jsonLdResponse } from "@response";
import { MatchedRoute } from "bun"; import { MatchedRoute } from "bun";
import { User } from "~database/entities/User"; import { User, userRelations } from "~database/entities/User";
import { getHost } from "@config"; import { getHost } from "@config";
import { NodeObject, compact } from "jsonld"; import { NodeObject, compact } from "jsonld";
import { RawActivity } from "~database/entities/RawActivity"; import { RawActivity } from "~database/entities/RawActivity";
@ -30,7 +30,10 @@ export default async (
const min_id = matchedRoute.query.min_id || false; const min_id = matchedRoute.query.min_id || false;
const max_id = matchedRoute.query.max_id || false; const max_id = matchedRoute.query.max_id || false;
const user = await User.findOneBy({ username }); const user = await User.findOne({
where: { username },
relations: userRelations,
});
if (!user) { if (!user) {
return errorResponse("User not found", 404); return errorResponse("User not found", 404);

View file

@ -5,7 +5,7 @@ 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 { User } from "~database/entities/User"; import { User, userRelations } from "~database/entities/User";
const config = getConfig(); const config = getConfig();
@ -65,8 +65,11 @@ describe("POST /@test/actor", () => {
afterAll(async () => { afterAll(async () => {
// Clean up user // Clean up user
const user = await User.findOneBy({ const user = await User.findOne({
username: "test", where: {
username: "test",
},
relations: userRelations,
}); });
const activities = await RawActivity.createQueryBuilder("activity") const activities = await RawActivity.createQueryBuilder("activity")

View file

@ -278,7 +278,7 @@ describe("API Tests", () => {
expect(account.created_at).toBeDefined(); expect(account.created_at).toBeDefined();
expect(account.followers_count).toBe(0); expect(account.followers_count).toBe(0);
expect(account.following_count).toBe(0); expect(account.following_count).toBe(0);
expect(account.statuses_count).toBe(0); expect(account.statuses_count).toBe(2);
expect(account.note).toBe(""); expect(account.note).toBe("");
expect(account.url).toBe( expect(account.url).toBe(
`${config.http.base_url}/users/${user.username}` `${config.http.base_url}/users/${user.username}`

View file

@ -3,7 +3,7 @@ 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 { Token } from "~database/entities/Token";
import { User } from "~database/entities/User"; import { User, userRelations } from "~database/entities/User";
const config = getConfig(); const config = getConfig();
@ -275,8 +275,11 @@ describe("POST /@test/inbox", () => {
afterAll(async () => { afterAll(async () => {
// Clean up user // Clean up user
const user = await User.findOneBy({ const user = await User.findOne({
username: "test", where: {
username: "test",
},
relations: userRelations,
}); });
// Clean up tokens // Clean up tokens

View file

@ -3,7 +3,7 @@ import { afterAll, beforeAll, describe, expect, test } from "bun:test";
import { AppDataSource } from "~database/datasource"; import { AppDataSource } from "~database/datasource";
import { Application } from "~database/entities/Application"; import { Application } from "~database/entities/Application";
import { Token } from "~database/entities/Token"; import { Token } from "~database/entities/Token";
import { User } from "~database/entities/User"; import { User, userRelations } from "~database/entities/User";
const config = getConfig(); const config = getConfig();
@ -150,8 +150,11 @@ describe("GET /api/v1/apps/verify_credentials", () => {
afterAll(async () => { afterAll(async () => {
// Clean up user // Clean up user
const user = await User.findOneBy({ const user = await User.findOne({
username: "test", where: {
username: "test",
},
relations: userRelations,
}); });
// Clean up tokens // Clean up tokens