mirror of
https://github.com/versia-pub/server.git
synced 2025-12-06 08:28:19 +01:00
docs: 📝 More work on JSDoc
This commit is contained in:
parent
527137f279
commit
c3271ba264
10
biome.json
10
biome.json
|
|
@ -48,6 +48,16 @@
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"nursery": {
|
||||||
|
"noDuplicateElseIf": "warn",
|
||||||
|
"noDuplicateJsonKeys": "warn",
|
||||||
|
"noEvolvingTypes": "warn",
|
||||||
|
"noYodaExpression": "warn",
|
||||||
|
"useConsistentBuiltinInstantiation": "warn",
|
||||||
|
"useErrorMessage": "warn",
|
||||||
|
"useImportExtensions": "off",
|
||||||
|
"useThrowNewError": "warn"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"ignore": ["node_modules", "dist", "glitch", "glitch-dev"]
|
"ignore": ["node_modules", "dist", "glitch", "glitch-dev"]
|
||||||
|
|
|
||||||
|
|
@ -69,7 +69,7 @@ export default class UserCreate extends BaseCommand<typeof UserCreate> {
|
||||||
this.exit(1);
|
this.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
let password = null;
|
let password: string | null = null;
|
||||||
|
|
||||||
if (flags["set-password"]) {
|
if (flags["set-password"]) {
|
||||||
const password1 = await input({
|
const password1 = await input({
|
||||||
|
|
|
||||||
|
|
@ -1,21 +1,60 @@
|
||||||
import type { InferModelFromColumns, InferSelectModel } from "drizzle-orm";
|
import type { InferModelFromColumns, InferSelectModel } from "drizzle-orm";
|
||||||
import type { PgTableWithColumns } from "drizzle-orm/pg-core";
|
import type { PgTableWithColumns } from "drizzle-orm/pg-core";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* BaseInterface is an abstract class that provides a common interface for all models.
|
||||||
|
* It includes methods for saving, deleting, updating, and reloading data.
|
||||||
|
*
|
||||||
|
* @template Table - The type of the table with columns.
|
||||||
|
* @template Columns - The type of the columns inferred from the table.
|
||||||
|
*/
|
||||||
export abstract class BaseInterface<
|
export abstract class BaseInterface<
|
||||||
// biome-ignore lint/suspicious/noExplicitAny: This is just an extended interface
|
// biome-ignore lint/suspicious/noExplicitAny: This is just an extended interface
|
||||||
Table extends PgTableWithColumns<any>,
|
Table extends PgTableWithColumns<any>,
|
||||||
Columns = InferModelFromColumns<Table["_"]["columns"]>,
|
Columns = InferModelFromColumns<Table["_"]["columns"]>,
|
||||||
> {
|
> {
|
||||||
|
/**
|
||||||
|
* Constructs a new instance of the BaseInterface.
|
||||||
|
*
|
||||||
|
* @param data - The data for the model.
|
||||||
|
*/
|
||||||
constructor(public data: Columns) {}
|
constructor(public data: Columns) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Saves the current state of the model to the database.
|
||||||
|
*
|
||||||
|
* @returns A promise that resolves with the saved model.
|
||||||
|
*/
|
||||||
public abstract save(): Promise<Columns>;
|
public abstract save(): Promise<Columns>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes the model from the database.
|
||||||
|
*
|
||||||
|
* @param ids - The ids of the models to delete.
|
||||||
|
* @returns A promise that resolves when the deletion is complete.
|
||||||
|
*/
|
||||||
public abstract delete(ids: string[]): Promise<void>;
|
public abstract delete(ids: string[]): Promise<void>;
|
||||||
|
/**
|
||||||
|
* Deletes the model from the database.
|
||||||
|
*
|
||||||
|
* @returns A promise that resolves when the deletion is complete.
|
||||||
|
*/
|
||||||
public abstract delete(): Promise<void>;
|
public abstract delete(): Promise<void>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the model with new data.
|
||||||
|
*
|
||||||
|
* @param newData - The new data for the model.
|
||||||
|
* @returns A promise that resolves with the updated model.
|
||||||
|
*/
|
||||||
public abstract update(
|
public abstract update(
|
||||||
newData: Partial<InferSelectModel<Table>>,
|
newData: Partial<InferSelectModel<Table>>,
|
||||||
): Promise<Columns>;
|
): Promise<Columns>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reloads the model from the database.
|
||||||
|
*
|
||||||
|
* @returns A promise that resolves when the reloading is complete.
|
||||||
|
*/
|
||||||
public abstract reload(): Promise<void>;
|
public abstract reload(): Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,7 @@ import {
|
||||||
} from "~/drizzle/schema";
|
} from "~/drizzle/schema";
|
||||||
import { config } from "~/packages/config-manager";
|
import { config } from "~/packages/config-manager";
|
||||||
import type { Attachment as apiAttachment } from "~/types/mastodon/attachment";
|
import type { Attachment as apiAttachment } from "~/types/mastodon/attachment";
|
||||||
import type { Status as apiStatus } from "~/types/mastodon/status";
|
import type { Status as APIStatus } from "~/types/mastodon/status";
|
||||||
import { Attachment } from "./attachment";
|
import { Attachment } from "./attachment";
|
||||||
import { BaseInterface } from "./base";
|
import { BaseInterface } from "./base";
|
||||||
import { Emoji } from "./emoji";
|
import { Emoji } from "./emoji";
|
||||||
|
|
@ -64,6 +64,12 @@ export class Note extends BaseInterface<typeof Notes, StatusWithRelations> {
|
||||||
this.data = reloaded.data;
|
this.data = reloaded.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Insert a new note into the database
|
||||||
|
* @param data - The data to insert
|
||||||
|
* @param userRequestingNoteId - The ID of the user requesting the note (used to check visibility of the note)
|
||||||
|
* @returns The inserted note
|
||||||
|
*/
|
||||||
public static async insert(
|
public static async insert(
|
||||||
data: InferInsertModel<typeof Notes>,
|
data: InferInsertModel<typeof Notes>,
|
||||||
userRequestingNoteId?: string,
|
userRequestingNoteId?: string,
|
||||||
|
|
@ -79,6 +85,12 @@ export class Note extends BaseInterface<typeof Notes, StatusWithRelations> {
|
||||||
return note;
|
return note;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch a note from the database by its ID
|
||||||
|
* @param id - The ID of the note to fetch
|
||||||
|
* @param userRequestingNoteId - The ID of the user requesting the note (used to check visibility of the note)
|
||||||
|
* @returns The fetched note
|
||||||
|
*/
|
||||||
static async fromId(
|
static async fromId(
|
||||||
id: string | null,
|
id: string | null,
|
||||||
userRequestingNoteId?: string,
|
userRequestingNoteId?: string,
|
||||||
|
|
@ -93,7 +105,12 @@ export class Note extends BaseInterface<typeof Notes, StatusWithRelations> {
|
||||||
userRequestingNoteId,
|
userRequestingNoteId,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* Fetch multiple notes from the database by their IDs
|
||||||
|
* @param ids - The IDs of the notes to fetch
|
||||||
|
* @param userRequestingNoteId - The ID of the user requesting the note (used to check visibility of the note)
|
||||||
|
* @returns The fetched notes
|
||||||
|
*/
|
||||||
static async fromIds(
|
static async fromIds(
|
||||||
ids: string[],
|
ids: string[],
|
||||||
userRequestingNoteId?: string,
|
userRequestingNoteId?: string,
|
||||||
|
|
@ -107,11 +124,18 @@ export class Note extends BaseInterface<typeof Notes, StatusWithRelations> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch a note from the database by a SQL query
|
||||||
|
* @param sql - The SQL query to fetch the note with
|
||||||
|
* @param orderBy - The SQL query to order the results by
|
||||||
|
* @param userId - The ID of the user requesting the note (used to check visibility of the note)
|
||||||
|
* @returns The fetched note
|
||||||
|
*/
|
||||||
static async fromSql(
|
static async fromSql(
|
||||||
sql: SQL<unknown> | undefined,
|
sql: SQL<unknown> | undefined,
|
||||||
orderBy: SQL<unknown> | undefined = desc(Notes.id),
|
orderBy: SQL<unknown> | undefined = desc(Notes.id),
|
||||||
userId?: string,
|
userId?: string,
|
||||||
) {
|
): Promise<Note | null> {
|
||||||
const found = await findManyNotes(
|
const found = await findManyNotes(
|
||||||
{
|
{
|
||||||
where: sql,
|
where: sql,
|
||||||
|
|
@ -127,13 +151,22 @@ export class Note extends BaseInterface<typeof Notes, StatusWithRelations> {
|
||||||
return new Note(found[0]);
|
return new Note(found[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch multiple notes from the database by a SQL query
|
||||||
|
* @param sql - The SQL query to fetch the notes with
|
||||||
|
* @param orderBy - The SQL query to order the results by
|
||||||
|
* @param limit - The maximum number of notes to fetch
|
||||||
|
* @param offset - The number of notes to skip
|
||||||
|
* @param userId - The ID of the user requesting the note (used to check visibility of the note)
|
||||||
|
* @returns - The fetched notes
|
||||||
|
*/
|
||||||
static async manyFromSql(
|
static async manyFromSql(
|
||||||
sql: SQL<unknown> | undefined,
|
sql: SQL<unknown> | undefined,
|
||||||
orderBy: SQL<unknown> | undefined = desc(Notes.id),
|
orderBy: SQL<unknown> | undefined = desc(Notes.id),
|
||||||
limit?: number,
|
limit?: number,
|
||||||
offset?: number,
|
offset?: number,
|
||||||
userId?: string,
|
userId?: string,
|
||||||
) {
|
): Promise<Note[]> {
|
||||||
const found = await findManyNotes(
|
const found = await findManyNotes(
|
||||||
{
|
{
|
||||||
where: sql,
|
where: sql,
|
||||||
|
|
@ -151,7 +184,15 @@ export class Note extends BaseInterface<typeof Notes, StatusWithRelations> {
|
||||||
return this.data.id;
|
return this.data.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getUsersToFederateTo() {
|
/**
|
||||||
|
* Fetch the users that should be federated to for this note
|
||||||
|
*
|
||||||
|
* This includes:
|
||||||
|
* - Users mentioned in the note
|
||||||
|
* - Users that can see the note
|
||||||
|
* @returns The users that should be federated to
|
||||||
|
*/
|
||||||
|
async getUsersToFederateTo(): Promise<User[]> {
|
||||||
// Mentioned users
|
// Mentioned users
|
||||||
const mentionedUsers =
|
const mentionedUsers =
|
||||||
this.data.mentions.length > 0
|
this.data.mentions.length > 0
|
||||||
|
|
@ -194,15 +235,15 @@ export class Note extends BaseInterface<typeof Notes, StatusWithRelations> {
|
||||||
return deduplicatedUsersById;
|
return deduplicatedUsersById;
|
||||||
}
|
}
|
||||||
|
|
||||||
isNull() {
|
|
||||||
return this.data === null;
|
|
||||||
}
|
|
||||||
|
|
||||||
get author() {
|
get author() {
|
||||||
return new User(this.data.author);
|
return new User(this.data.author);
|
||||||
}
|
}
|
||||||
|
|
||||||
static async getCount() {
|
/**
|
||||||
|
* Get the number of notes in the database (excluding remote notes)
|
||||||
|
* @returns The number of notes in the database
|
||||||
|
*/
|
||||||
|
static async getCount(): Promise<number> {
|
||||||
return (
|
return (
|
||||||
await db
|
await db
|
||||||
.select({
|
.select({
|
||||||
|
|
@ -215,7 +256,12 @@ export class Note extends BaseInterface<typeof Notes, StatusWithRelations> {
|
||||||
)[0].count;
|
)[0].count;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getReplyChildren(userId?: string) {
|
/**
|
||||||
|
* Get the children of this note (replies)
|
||||||
|
* @param userId - The ID of the user requesting the note (used to check visibility of the note)
|
||||||
|
* @returns The children of this note
|
||||||
|
*/
|
||||||
|
private async getReplyChildren(userId?: string): Promise<Note[]> {
|
||||||
return await Note.manyFromSql(
|
return await Note.manyFromSql(
|
||||||
eq(Notes.replyId, this.data.id),
|
eq(Notes.replyId, this.data.id),
|
||||||
undefined,
|
undefined,
|
||||||
|
|
@ -229,7 +275,11 @@ export class Note extends BaseInterface<typeof Notes, StatusWithRelations> {
|
||||||
return this.author.isRemote();
|
return this.author.isRemote();
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateFromRemote() {
|
/**
|
||||||
|
* Update a note from remote federated servers
|
||||||
|
* @returns The updated note
|
||||||
|
*/
|
||||||
|
async updateFromRemote(): Promise<Note> {
|
||||||
if (!this.isRemote()) {
|
if (!this.isRemote()) {
|
||||||
throw new Error("Cannot refetch a local note (it is not remote)");
|
throw new Error("Cannot refetch a local note (it is not remote)");
|
||||||
}
|
}
|
||||||
|
|
@ -245,10 +295,15 @@ export class Note extends BaseInterface<typeof Notes, StatusWithRelations> {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new note from user input
|
||||||
|
* @param data - The data to create the note from
|
||||||
|
* @returns The created note
|
||||||
|
*/
|
||||||
static async fromData(data: {
|
static async fromData(data: {
|
||||||
author: User;
|
author: User;
|
||||||
content: typeof EntityValidator.$ContentFormat;
|
content: typeof EntityValidator.$ContentFormat;
|
||||||
visibility: apiStatus["visibility"];
|
visibility: APIStatus["visibility"];
|
||||||
isSensitive: boolean;
|
isSensitive: boolean;
|
||||||
spoilerText: string;
|
spoilerText: string;
|
||||||
emojis?: Emoji[];
|
emojis?: Emoji[];
|
||||||
|
|
@ -330,10 +385,15 @@ export class Note extends BaseInterface<typeof Notes, StatusWithRelations> {
|
||||||
return newNote;
|
return newNote;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update a note from user input
|
||||||
|
* @param data - The data to update the note from
|
||||||
|
* @returns The updated note
|
||||||
|
*/
|
||||||
async updateFromData(data: {
|
async updateFromData(data: {
|
||||||
author?: User;
|
author?: User;
|
||||||
content?: typeof EntityValidator.$ContentFormat;
|
content?: typeof EntityValidator.$ContentFormat;
|
||||||
visibility?: apiStatus["visibility"];
|
visibility?: APIStatus["visibility"];
|
||||||
isSensitive?: boolean;
|
isSensitive?: boolean;
|
||||||
spoilerText?: string;
|
spoilerText?: string;
|
||||||
emojis?: Emoji[];
|
emojis?: Emoji[];
|
||||||
|
|
@ -405,6 +465,12 @@ export class Note extends BaseInterface<typeof Notes, StatusWithRelations> {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the emojis associated with this note in the database
|
||||||
|
*
|
||||||
|
* Deletes all existing emojis associated with this note, then replaces them with the provided emojis.
|
||||||
|
* @param emojis - The emojis to associate with this note
|
||||||
|
*/
|
||||||
public async recalculateDatabaseEmojis(emojis: Emoji[]): Promise<void> {
|
public async recalculateDatabaseEmojis(emojis: Emoji[]): Promise<void> {
|
||||||
// Fuse and deduplicate
|
// Fuse and deduplicate
|
||||||
const fusedEmojis = emojis.filter(
|
const fusedEmojis = emojis.filter(
|
||||||
|
|
@ -428,6 +494,12 @@ export class Note extends BaseInterface<typeof Notes, StatusWithRelations> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the mentions associated with this note in the database
|
||||||
|
*
|
||||||
|
* Deletes all existing mentions associated with this note, then replaces them with the provided mentions.
|
||||||
|
* @param mentions - The mentions to associate with this note
|
||||||
|
*/
|
||||||
public async recalculateDatabaseMentions(mentions: User[]): Promise<void> {
|
public async recalculateDatabaseMentions(mentions: User[]): Promise<void> {
|
||||||
// Connect mentions
|
// Connect mentions
|
||||||
await db
|
await db
|
||||||
|
|
@ -445,6 +517,12 @@ export class Note extends BaseInterface<typeof Notes, StatusWithRelations> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the attachments associated with this note in the database
|
||||||
|
*
|
||||||
|
* Deletes all existing attachments associated with this note, then replaces them with the provided attachments.
|
||||||
|
* @param mediaAttachments - The IDs of the attachments to associate with this note
|
||||||
|
*/
|
||||||
public async recalculateDatabaseAttachments(
|
public async recalculateDatabaseAttachments(
|
||||||
mediaAttachments: string[],
|
mediaAttachments: string[],
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
|
|
@ -466,19 +544,21 @@ export class Note extends BaseInterface<typeof Notes, StatusWithRelations> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static async resolve(
|
/**
|
||||||
uri?: string,
|
* Resolve a note from a URI
|
||||||
providedNote?: typeof EntityValidator.$Note,
|
* @param uri - The URI of the note to resolve
|
||||||
): Promise<Note | null> {
|
* @returns The resolved note
|
||||||
|
*/
|
||||||
|
static async resolve(uri: string): Promise<Note | null> {
|
||||||
// Check if note not already in database
|
// Check if note not already in database
|
||||||
const foundNote = uri && (await Note.fromSql(eq(Notes.uri, uri)));
|
const foundNote = await Note.fromSql(eq(Notes.uri, uri));
|
||||||
|
|
||||||
if (foundNote) {
|
if (foundNote) {
|
||||||
return foundNote;
|
return foundNote;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if URI is of a local note
|
// Check if URI is of a local note
|
||||||
if (uri?.startsWith(config.http.base_url)) {
|
if (uri.startsWith(config.http.base_url)) {
|
||||||
const uuid = uri.match(idValidator);
|
const uuid = uri.match(idValidator);
|
||||||
|
|
||||||
if (!uuid?.[0]) {
|
if (!uuid?.[0]) {
|
||||||
|
|
@ -490,18 +570,16 @@ export class Note extends BaseInterface<typeof Notes, StatusWithRelations> {
|
||||||
return await Note.fromId(uuid[0]);
|
return await Note.fromId(uuid[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
return await Note.saveFromRemote(uri, providedNote);
|
return await Note.saveFromRemote(uri);
|
||||||
}
|
}
|
||||||
|
|
||||||
static async saveFromRemote(
|
/**
|
||||||
uri?: string,
|
* Save a note from a remote server
|
||||||
providedNote?: typeof EntityValidator.$Note,
|
* @param uri - The URI of the note to save
|
||||||
): Promise<Note | null> {
|
* @returns The saved note, or null if the note could not be fetched
|
||||||
if (!(uri || providedNote)) {
|
*/
|
||||||
throw new Error("No URI or note provided");
|
static async saveFromRemote(uri: string): Promise<Note | null> {
|
||||||
}
|
let note: typeof EntityValidator.$Note | null = null;
|
||||||
|
|
||||||
let note = providedNote || null;
|
|
||||||
|
|
||||||
if (uri) {
|
if (uri) {
|
||||||
if (!URL.canParse(uri)) {
|
if (!URL.canParse(uri)) {
|
||||||
|
|
@ -531,11 +609,17 @@ export class Note extends BaseInterface<typeof Notes, StatusWithRelations> {
|
||||||
return await Note.fromLysand(note, author);
|
return await Note.fromLysand(note, author);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Turns a Lysand Note into a database note (saved)
|
||||||
|
* @param note Lysand Note
|
||||||
|
* @param author Author of the note
|
||||||
|
* @returns The saved note
|
||||||
|
*/
|
||||||
static async fromLysand(
|
static async fromLysand(
|
||||||
note: typeof EntityValidator.$Note,
|
note: typeof EntityValidator.$Note,
|
||||||
author: User,
|
author: User,
|
||||||
): Promise<Note> {
|
): Promise<Note> {
|
||||||
const emojis = [];
|
const emojis: Emoji[] = [];
|
||||||
|
|
||||||
for (const emoji of note.extensions?.["org.lysand:custom_emojis"]
|
for (const emoji of note.extensions?.["org.lysand:custom_emojis"]
|
||||||
?.emojis ?? []) {
|
?.emojis ?? []) {
|
||||||
|
|
@ -555,7 +639,7 @@ export class Note extends BaseInterface<typeof Notes, StatusWithRelations> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const attachments = [];
|
const attachments: Attachment[] = [];
|
||||||
|
|
||||||
for (const attachment of note.attachments ?? []) {
|
for (const attachment of note.attachments ?? []) {
|
||||||
const resolvedAttachment = await Attachment.fromLysand(
|
const resolvedAttachment = await Attachment.fromLysand(
|
||||||
|
|
@ -581,7 +665,7 @@ export class Note extends BaseInterface<typeof Notes, StatusWithRelations> {
|
||||||
content: "",
|
content: "",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
visibility: note.visibility as apiStatus["visibility"],
|
visibility: note.visibility as APIStatus["visibility"],
|
||||||
isSensitive: note.is_sensitive ?? false,
|
isSensitive: note.is_sensitive ?? false,
|
||||||
spoilerText: note.subject ?? "",
|
spoilerText: note.subject ?? "",
|
||||||
emojis,
|
emojis,
|
||||||
|
|
@ -644,7 +728,7 @@ export class Note extends BaseInterface<typeof Notes, StatusWithRelations> {
|
||||||
* @param user The user to check.
|
* @param user The user to check.
|
||||||
* @returns Whether this status is viewable by the user.
|
* @returns Whether this status is viewable by the user.
|
||||||
*/
|
*/
|
||||||
async isViewableByUser(user: User | null) {
|
async isViewableByUser(user: User | null): Promise<boolean> {
|
||||||
if (this.author.id === user?.id) {
|
if (this.author.id === user?.id) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
@ -656,22 +740,28 @@ export class Note extends BaseInterface<typeof Notes, StatusWithRelations> {
|
||||||
}
|
}
|
||||||
if (this.data.visibility === "private") {
|
if (this.data.visibility === "private") {
|
||||||
return user
|
return user
|
||||||
? await db.query.Relationships.findFirst({
|
? !!(await db.query.Relationships.findFirst({
|
||||||
where: (relationship, { and, eq }) =>
|
where: (relationship, { and, eq }) =>
|
||||||
and(
|
and(
|
||||||
eq(relationship.ownerId, user?.id),
|
eq(relationship.ownerId, user?.id),
|
||||||
eq(relationship.subjectId, Notes.authorId),
|
eq(relationship.subjectId, Notes.authorId),
|
||||||
eq(relationship.following, true),
|
eq(relationship.following, true),
|
||||||
),
|
),
|
||||||
})
|
}))
|
||||||
: false;
|
: false;
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
user && this.data.mentions.find((mention) => mention.id === user.id)
|
!!user &&
|
||||||
|
!!this.data.mentions.find((mention) => mention.id === user.id)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async toApi(userFetching?: User | null): Promise<apiStatus> {
|
/**
|
||||||
|
* Convert a note to the Mastodon API format
|
||||||
|
* @param userFetching - The user fetching the note (used to check if the note is favourite and such)
|
||||||
|
* @returns The note in the Mastodon API format
|
||||||
|
*/
|
||||||
|
async toApi(userFetching?: User | null): Promise<APIStatus> {
|
||||||
const data = this.data;
|
const data = this.data;
|
||||||
|
|
||||||
// Convert mentions of local users from @username@host to @username
|
// Convert mentions of local users from @username@host to @username
|
||||||
|
|
@ -749,7 +839,7 @@ export class Note extends BaseInterface<typeof Notes, StatusWithRelations> {
|
||||||
spoiler_text: data.spoilerText,
|
spoiler_text: data.spoilerText,
|
||||||
tags: [],
|
tags: [],
|
||||||
uri: data.uri || this.getUri(),
|
uri: data.uri || this.getUri(),
|
||||||
visibility: data.visibility as apiStatus["visibility"],
|
visibility: data.visibility as APIStatus["visibility"],
|
||||||
url: data.uri || this.getMastoUri(),
|
url: data.uri || this.getMastoUri(),
|
||||||
bookmarked: false,
|
bookmarked: false,
|
||||||
// @ts-expect-error Glitch-SOC extension
|
// @ts-expect-error Glitch-SOC extension
|
||||||
|
|
@ -762,24 +852,32 @@ export class Note extends BaseInterface<typeof Notes, StatusWithRelations> {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
getUri() {
|
getUri(): string {
|
||||||
return localObjectUri(this.data.id);
|
return localObjectUri(this.data.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
static getUri(id?: string | null) {
|
static getUri(id?: string | null): string | null {
|
||||||
if (!id) {
|
if (!id) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return localObjectUri(id);
|
return localObjectUri(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
getMastoUri() {
|
/**
|
||||||
|
* Get the frontend URI of this note
|
||||||
|
* @returns The frontend URI of this note
|
||||||
|
*/
|
||||||
|
getMastoUri(): string {
|
||||||
return new URL(
|
return new URL(
|
||||||
`/@${this.author.data.username}/${this.id}`,
|
`/@${this.author.data.username}/${this.id}`,
|
||||||
config.http.base_url,
|
config.http.base_url,
|
||||||
).toString();
|
).toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert a note to the Lysand format
|
||||||
|
* @returns The note in the Lysand format
|
||||||
|
*/
|
||||||
toLysand(): typeof EntityValidator.$Note {
|
toLysand(): typeof EntityValidator.$Note {
|
||||||
const status = this.data;
|
const status = this.data;
|
||||||
return {
|
return {
|
||||||
|
|
@ -822,8 +920,11 @@ export class Note extends BaseInterface<typeof Notes, StatusWithRelations> {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return all the ancestors of this post,
|
* Return all the ancestors of this post,
|
||||||
|
* i.e. all the posts that this post is a reply to
|
||||||
|
* @param fetcher - The user fetching the ancestors
|
||||||
|
* @returns The ancestors of this post
|
||||||
*/
|
*/
|
||||||
async getAncestors(fetcher: User | null) {
|
async getAncestors(fetcher: User | null): Promise<Note[]> {
|
||||||
const ancestors: Note[] = [];
|
const ancestors: Note[] = [];
|
||||||
|
|
||||||
let currentStatus: Note = this;
|
let currentStatus: Note = this;
|
||||||
|
|
@ -854,8 +955,11 @@ export class Note extends BaseInterface<typeof Notes, StatusWithRelations> {
|
||||||
/**
|
/**
|
||||||
* Return all the descendants of this post (recursive)
|
* Return all the descendants of this post (recursive)
|
||||||
* Temporary implementation, will be replaced with a recursive SQL query when I get to it
|
* Temporary implementation, will be replaced with a recursive SQL query when I get to it
|
||||||
|
* @param fetcher - The user fetching the descendants
|
||||||
|
* @param depth - The depth of the recursion (internal)
|
||||||
|
* @returns The descendants of this post
|
||||||
*/
|
*/
|
||||||
async getDescendants(fetcher: User | null, depth = 0) {
|
async getDescendants(fetcher: User | null, depth = 0): Promise<Note[]> {
|
||||||
const descendants: Note[] = [];
|
const descendants: Note[] = [];
|
||||||
for (const child of await this.getReplyChildren(fetcher?.id)) {
|
for (const child of await this.getReplyChildren(fetcher?.id)) {
|
||||||
descendants.push(child);
|
descendants.push(child);
|
||||||
|
|
|
||||||
|
|
@ -66,7 +66,7 @@ export class Timeline<Type extends Note | User> {
|
||||||
url: string,
|
url: string,
|
||||||
limit: number,
|
limit: number,
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
const linkHeader = [];
|
const linkHeader: string[] = [];
|
||||||
const urlWithoutQuery = new URL(
|
const urlWithoutQuery = new URL(
|
||||||
new URL(url).pathname,
|
new URL(url).pathname,
|
||||||
config.http.base_url,
|
config.http.base_url,
|
||||||
|
|
@ -103,7 +103,7 @@ export class Timeline<Type extends Note | User> {
|
||||||
urlWithoutQuery: string,
|
urlWithoutQuery: string,
|
||||||
limit: number,
|
limit: number,
|
||||||
): Promise<string[]> {
|
): Promise<string[]> {
|
||||||
const linkHeader = [];
|
const linkHeader: string[] = [];
|
||||||
|
|
||||||
const objectBefore = await Note.fromSql(gt(Notes.id, notes[0].data.id));
|
const objectBefore = await Note.fromSql(gt(Notes.id, notes[0].data.id));
|
||||||
if (objectBefore) {
|
if (objectBefore) {
|
||||||
|
|
@ -131,7 +131,7 @@ export class Timeline<Type extends Note | User> {
|
||||||
urlWithoutQuery: string,
|
urlWithoutQuery: string,
|
||||||
limit: number,
|
limit: number,
|
||||||
): Promise<string[]> {
|
): Promise<string[]> {
|
||||||
const linkHeader = [];
|
const linkHeader: string[] = [];
|
||||||
|
|
||||||
const objectBefore = await User.fromSql(gt(Users.id, users[0].id));
|
const objectBefore = await User.fromSql(gt(Users.id, users[0].id));
|
||||||
if (objectBefore) {
|
if (objectBefore) {
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ import {
|
||||||
MediaBackendType,
|
MediaBackendType,
|
||||||
MediaHasher,
|
MediaHasher,
|
||||||
S3MediaBackend,
|
S3MediaBackend,
|
||||||
} from "..";
|
} from "../index";
|
||||||
import { MediaConverter } from "../media-converter";
|
import { MediaConverter } from "../media-converter";
|
||||||
|
|
||||||
type DeepPartial<T> = {
|
type DeepPartial<T> = {
|
||||||
|
|
|
||||||
|
|
@ -204,9 +204,9 @@ export default (app: Hono) =>
|
||||||
return errorResponse("Author not found", 404);
|
return errorResponse("Author not found", 404);
|
||||||
}
|
}
|
||||||
|
|
||||||
const newStatus = await Note.resolve(
|
const newStatus = await Note.fromLysand(
|
||||||
undefined,
|
|
||||||
note,
|
note,
|
||||||
|
account,
|
||||||
).catch((e) => {
|
).catch((e) => {
|
||||||
dualLogger.logError(
|
dualLogger.logError(
|
||||||
LogLevel.Error,
|
LogLevel.Error,
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ export async function fetchTimeline<T extends UserType | Status | Notification>(
|
||||||
const objects = (await model(args, userId)) as T[];
|
const objects = (await model(args, userId)) as T[];
|
||||||
|
|
||||||
// Constuct HTTP Link header (next and prev) only if there are more statuses
|
// Constuct HTTP Link header (next and prev) only if there are more statuses
|
||||||
const linkHeader = [];
|
const linkHeader: string[] = [];
|
||||||
const urlWithoutQuery = new URL(
|
const urlWithoutQuery = new URL(
|
||||||
new URL(req.url).pathname,
|
new URL(req.url).pathname,
|
||||||
config.http.base_url,
|
config.http.base_url,
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue