mirror of
https://github.com/versia-pub/server.git
synced 2026-03-13 05:49:16 +01:00
refactor(database): 🎨 Improve database handlers to have more consistent naming and methods
This commit is contained in:
parent
a6159b9d55
commit
5565bf00de
47 changed files with 365 additions and 333 deletions
21
packages/database-interface/base.ts
Normal file
21
packages/database-interface/base.ts
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
import type { InferModelFromColumns, InferSelectModel } from "drizzle-orm";
|
||||
import type { PgTableWithColumns } from "drizzle-orm/pg-core";
|
||||
|
||||
export abstract class BaseInterface<
|
||||
// biome-ignore lint/suspicious/noExplicitAny: This is just an extended interface
|
||||
Table extends PgTableWithColumns<any>,
|
||||
Columns = InferModelFromColumns<Table["_"]["columns"]>,
|
||||
> {
|
||||
constructor(public data: Columns) {}
|
||||
|
||||
public abstract save(): Promise<Columns>;
|
||||
|
||||
public abstract delete(ids: string[]): Promise<void>;
|
||||
public abstract delete(): Promise<void>;
|
||||
|
||||
public abstract update(
|
||||
newData: Partial<InferSelectModel<Table>>,
|
||||
): Promise<Columns>;
|
||||
|
||||
public abstract reload(): Promise<void>;
|
||||
}
|
||||
|
|
@ -35,7 +35,6 @@ import {
|
|||
} from "~/database/entities/Emoji";
|
||||
import { localObjectURI } from "~/database/entities/Federation";
|
||||
import {
|
||||
type Status,
|
||||
type StatusWithRelations,
|
||||
contentToHtml,
|
||||
findManyNotes,
|
||||
|
|
@ -52,30 +51,65 @@ import {
|
|||
import { config } from "~/packages/config-manager";
|
||||
import type { Attachment as APIAttachment } from "~/types/mastodon/attachment";
|
||||
import type { Status as APIStatus } from "~/types/mastodon/status";
|
||||
import { BaseInterface } from "./base";
|
||||
import { User } from "./user";
|
||||
|
||||
/**
|
||||
* Gives helpers to fetch notes from database in a nice format
|
||||
*/
|
||||
export class Note {
|
||||
private constructor(private status: StatusWithRelations) {}
|
||||
export class Note extends BaseInterface<typeof Notes, StatusWithRelations> {
|
||||
async save(): Promise<StatusWithRelations> {
|
||||
return this.update(this.data);
|
||||
}
|
||||
|
||||
async reload(): Promise<void> {
|
||||
const reloaded = await Note.fromId(this.data.id);
|
||||
|
||||
if (!reloaded) {
|
||||
throw new Error("Failed to reload status");
|
||||
}
|
||||
|
||||
this.data = reloaded.data;
|
||||
}
|
||||
|
||||
public static async insert(
|
||||
data: InferInsertModel<typeof Notes>,
|
||||
userRequestingNoteId?: string,
|
||||
): Promise<Note> {
|
||||
const inserted = (await db.insert(Notes).values(data).returning())[0];
|
||||
|
||||
const note = await Note.fromId(inserted.id, userRequestingNoteId);
|
||||
|
||||
if (!note) {
|
||||
throw new Error("Failed to insert status");
|
||||
}
|
||||
|
||||
return note;
|
||||
}
|
||||
|
||||
static async fromId(
|
||||
id: string | null,
|
||||
userId?: string,
|
||||
userRequestingNoteId?: string,
|
||||
): Promise<Note | null> {
|
||||
if (!id) return null;
|
||||
|
||||
return await Note.fromSql(eq(Notes.id, id), undefined, userId);
|
||||
return await Note.fromSql(
|
||||
eq(Notes.id, id),
|
||||
undefined,
|
||||
userRequestingNoteId,
|
||||
);
|
||||
}
|
||||
|
||||
static async fromIds(ids: string[], userId?: string): Promise<Note[]> {
|
||||
static async fromIds(
|
||||
ids: string[],
|
||||
userRequestingNoteId?: string,
|
||||
): Promise<Note[]> {
|
||||
return await Note.manyFromSql(
|
||||
inArray(Notes.id, ids),
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
userId,
|
||||
userRequestingNoteId,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -118,21 +152,19 @@ export class Note {
|
|||
}
|
||||
|
||||
get id() {
|
||||
return this.status.id;
|
||||
return this.data.id;
|
||||
}
|
||||
|
||||
async getUsersToFederateTo() {
|
||||
// Mentioned users
|
||||
const mentionedUsers =
|
||||
this.getStatus().mentions.length > 0
|
||||
this.data.mentions.length > 0
|
||||
? await User.manyFromSql(
|
||||
and(
|
||||
isNotNull(Users.instanceId),
|
||||
inArray(
|
||||
Users.id,
|
||||
this.getStatus().mentions.map(
|
||||
(mention) => mention.id,
|
||||
),
|
||||
this.data.mentions.map((mention) => mention.id),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
|
@ -166,24 +198,12 @@ export class Note {
|
|||
return deduplicatedUsersById;
|
||||
}
|
||||
|
||||
static fromStatus(status: StatusWithRelations) {
|
||||
return new Note(status);
|
||||
}
|
||||
|
||||
static fromStatuses(statuses: StatusWithRelations[]) {
|
||||
return statuses.map((s) => new Note(s));
|
||||
}
|
||||
|
||||
isNull() {
|
||||
return this.status === null;
|
||||
return this.data === null;
|
||||
}
|
||||
|
||||
getStatus() {
|
||||
return this.status;
|
||||
}
|
||||
|
||||
getAuthor() {
|
||||
return new User(this.status.author);
|
||||
get author() {
|
||||
return new User(this.data.author);
|
||||
}
|
||||
|
||||
static async getCount() {
|
||||
|
|
@ -201,7 +221,7 @@ export class Note {
|
|||
|
||||
async getReplyChildren(userId?: string) {
|
||||
return await Note.manyFromSql(
|
||||
eq(Notes.replyId, this.status.id),
|
||||
eq(Notes.replyId, this.data.id),
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
|
|
@ -209,12 +229,8 @@ export class Note {
|
|||
);
|
||||
}
|
||||
|
||||
static async insert(values: InferInsertModel<typeof Notes>) {
|
||||
return (await db.insert(Notes).values(values).returning())[0];
|
||||
}
|
||||
|
||||
async isRemote() {
|
||||
return this.getAuthor().isRemote();
|
||||
return this.author.isRemote();
|
||||
}
|
||||
|
||||
async updateFromRemote() {
|
||||
|
|
@ -222,13 +238,13 @@ export class Note {
|
|||
throw new Error("Cannot refetch a local note (it is not remote)");
|
||||
}
|
||||
|
||||
const updated = await Note.saveFromRemote(this.getURI());
|
||||
const updated = await Note.saveFromRemote(this.getUri());
|
||||
|
||||
if (!updated) {
|
||||
throw new Error("Note not found after update");
|
||||
}
|
||||
|
||||
this.status = updated.getStatus();
|
||||
this.data = updated.data;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
|
@ -324,7 +340,7 @@ export class Note {
|
|||
}
|
||||
}
|
||||
|
||||
return await Note.fromId(newNote.id, newNote.authorId);
|
||||
return await Note.fromId(newNote.id, newNote.data.authorId);
|
||||
}
|
||||
|
||||
async updateFromData(
|
||||
|
|
@ -347,7 +363,7 @@ export class Note {
|
|||
// Parse emojis and fuse with existing emojis
|
||||
let foundEmojis = emojis;
|
||||
|
||||
if (this.getAuthor().isLocal() && htmlContent) {
|
||||
if (this.author.isLocal() && htmlContent) {
|
||||
const parsedEmojis = await parseEmojis(htmlContent);
|
||||
// Fuse and deduplicate
|
||||
foundEmojis = [...emojis, ...parsedEmojis].filter(
|
||||
|
|
@ -376,14 +392,14 @@ export class Note {
|
|||
// Connect emojis
|
||||
await db
|
||||
.delete(EmojiToNote)
|
||||
.where(eq(EmojiToNote.noteId, this.status.id));
|
||||
.where(eq(EmojiToNote.noteId, this.data.id));
|
||||
|
||||
for (const emoji of foundEmojis) {
|
||||
await db
|
||||
.insert(EmojiToNote)
|
||||
.values({
|
||||
emojiId: emoji.id,
|
||||
noteId: this.status.id,
|
||||
noteId: this.data.id,
|
||||
})
|
||||
.execute();
|
||||
}
|
||||
|
|
@ -391,13 +407,13 @@ export class Note {
|
|||
// Connect mentions
|
||||
await db
|
||||
.delete(NoteToMentions)
|
||||
.where(eq(NoteToMentions.noteId, this.status.id));
|
||||
.where(eq(NoteToMentions.noteId, this.data.id));
|
||||
|
||||
for (const mention of mentions ?? []) {
|
||||
await db
|
||||
.insert(NoteToMentions)
|
||||
.values({
|
||||
noteId: this.status.id,
|
||||
noteId: this.data.id,
|
||||
userId: mention.id,
|
||||
})
|
||||
.execute();
|
||||
|
|
@ -410,13 +426,13 @@ export class Note {
|
|||
.set({
|
||||
noteId: null,
|
||||
})
|
||||
.where(eq(Attachments.noteId, this.status.id));
|
||||
.where(eq(Attachments.noteId, this.data.id));
|
||||
|
||||
if (media_attachments.length > 0)
|
||||
await db
|
||||
.update(Attachments)
|
||||
.set({
|
||||
noteId: this.status.id,
|
||||
noteId: this.data.id,
|
||||
})
|
||||
.where(inArray(Attachments.id, media_attachments));
|
||||
}
|
||||
|
|
@ -555,10 +571,10 @@ export class Note {
|
|||
: [],
|
||||
attachments.map((a) => a.id),
|
||||
note.replies_to
|
||||
? (await Note.resolve(note.replies_to))?.getStatus().id
|
||||
? (await Note.resolve(note.replies_to))?.data.id
|
||||
: undefined,
|
||||
note.quotes
|
||||
? (await Note.resolve(note.quotes))?.getStatus().id
|
||||
? (await Note.resolve(note.quotes))?.data.id
|
||||
: undefined,
|
||||
);
|
||||
}
|
||||
|
|
@ -582,10 +598,10 @@ export class Note {
|
|||
),
|
||||
attachments.map((a) => a.id),
|
||||
note.replies_to
|
||||
? (await Note.resolve(note.replies_to))?.getStatus().id
|
||||
? (await Note.resolve(note.replies_to))?.data.id
|
||||
: undefined,
|
||||
note.quotes
|
||||
? (await Note.resolve(note.quotes))?.getStatus().id
|
||||
? (await Note.resolve(note.quotes))?.data.id
|
||||
: undefined,
|
||||
);
|
||||
|
||||
|
|
@ -596,27 +612,28 @@ export class Note {
|
|||
return createdNote;
|
||||
}
|
||||
|
||||
async delete() {
|
||||
return (
|
||||
await db
|
||||
.delete(Notes)
|
||||
.where(eq(Notes.id, this.status.id))
|
||||
.returning()
|
||||
)[0];
|
||||
async delete(ids: string[]): Promise<void>;
|
||||
async delete(): Promise<void>;
|
||||
async delete(ids?: unknown): Promise<void> {
|
||||
if (Array.isArray(ids)) {
|
||||
await db.delete(Notes).where(inArray(Notes.id, ids));
|
||||
} else {
|
||||
await db.delete(Notes).where(eq(Notes.id, this.id));
|
||||
}
|
||||
}
|
||||
|
||||
async update(newStatus: Partial<Status>) {
|
||||
return (
|
||||
await db
|
||||
.update(Notes)
|
||||
.set(newStatus)
|
||||
.where(eq(Notes.id, this.status.id))
|
||||
.returning()
|
||||
)[0];
|
||||
}
|
||||
async update(
|
||||
newStatus: Partial<StatusWithRelations>,
|
||||
): Promise<StatusWithRelations> {
|
||||
await db.update(Notes).set(newStatus).where(eq(Notes.id, this.data.id));
|
||||
|
||||
static async deleteMany(ids: string[]) {
|
||||
return await db.delete(Notes).where(inArray(Notes.id, ids)).returning();
|
||||
const updated = await Note.fromId(this.data.id);
|
||||
|
||||
if (!updated) {
|
||||
throw new Error("Failed to update status");
|
||||
}
|
||||
|
||||
return updated.data;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -625,10 +642,10 @@ export class Note {
|
|||
* @returns Whether this status is viewable by the user.
|
||||
*/
|
||||
async isViewableByUser(user: User | null) {
|
||||
if (this.getAuthor().id === user?.id) return true;
|
||||
if (this.getStatus().visibility === "public") return true;
|
||||
if (this.getStatus().visibility === "unlisted") return true;
|
||||
if (this.getStatus().visibility === "private") {
|
||||
if (this.author.id === user?.id) return true;
|
||||
if (this.data.visibility === "public") return true;
|
||||
if (this.data.visibility === "unlisted") return true;
|
||||
if (this.data.visibility === "private") {
|
||||
return user
|
||||
? await db.query.Relationships.findFirst({
|
||||
where: (relationship, { and, eq }) =>
|
||||
|
|
@ -641,13 +658,12 @@ export class Note {
|
|||
: false;
|
||||
}
|
||||
return (
|
||||
user &&
|
||||
this.getStatus().mentions.find((mention) => mention.id === user.id)
|
||||
user && this.data.mentions.find((mention) => mention.id === user.id)
|
||||
);
|
||||
}
|
||||
|
||||
async toAPI(userFetching?: User | null): Promise<APIStatus> {
|
||||
const data = this.getStatus();
|
||||
const data = this.data;
|
||||
|
||||
// Convert mentions of local users from @username@host to @username
|
||||
const mentionedLocalUsers = data.mentions.filter(
|
||||
|
|
@ -684,7 +700,7 @@ export class Note {
|
|||
id: data.id,
|
||||
in_reply_to_id: data.replyId || null,
|
||||
in_reply_to_account_id: data.reply?.authorId || null,
|
||||
account: this.getAuthor().toAPI(userFetching?.id === data.authorId),
|
||||
account: this.author.toAPI(userFetching?.id === data.authorId),
|
||||
created_at: new Date(data.createdAt).toISOString(),
|
||||
application: data.application
|
||||
? applicationToAPI(data.application)
|
||||
|
|
@ -713,9 +729,9 @@ export class Note {
|
|||
// TODO: Add polls
|
||||
poll: null,
|
||||
reblog: data.reblog
|
||||
? await Note.fromStatus(
|
||||
data.reblog as StatusWithRelations,
|
||||
).toAPI(userFetching)
|
||||
? await new Note(data.reblog as StatusWithRelations).toAPI(
|
||||
userFetching,
|
||||
)
|
||||
: null,
|
||||
reblogged: data.reblogged,
|
||||
reblogs_count: data.reblogCount,
|
||||
|
|
@ -723,9 +739,9 @@ export class Note {
|
|||
sensitive: data.sensitive,
|
||||
spoiler_text: data.spoilerText,
|
||||
tags: [],
|
||||
uri: data.uri || this.getURI(),
|
||||
uri: data.uri || this.getUri(),
|
||||
visibility: data.visibility as APIStatus["visibility"],
|
||||
url: data.uri || this.getMastoURI(),
|
||||
url: data.uri || this.getMastoUri(),
|
||||
bookmarked: false,
|
||||
// @ts-expect-error Glitch-SOC extension
|
||||
quote: data.quotingId
|
||||
|
|
@ -737,30 +753,30 @@ export class Note {
|
|||
};
|
||||
}
|
||||
|
||||
getURI() {
|
||||
return localObjectURI(this.getStatus().id);
|
||||
getUri() {
|
||||
return localObjectURI(this.data.id);
|
||||
}
|
||||
|
||||
static getURI(id?: string | null) {
|
||||
static getUri(id?: string | null) {
|
||||
if (!id) return null;
|
||||
return localObjectURI(id);
|
||||
}
|
||||
|
||||
getMastoURI() {
|
||||
getMastoUri() {
|
||||
return new URL(
|
||||
`/@${this.getAuthor().getUser().username}/${this.id}`,
|
||||
`/@${this.author.data.username}/${this.id}`,
|
||||
config.http.base_url,
|
||||
).toString();
|
||||
}
|
||||
|
||||
toLysand(): typeof EntityValidator.$Note {
|
||||
const status = this.getStatus();
|
||||
const status = this.data;
|
||||
return {
|
||||
type: "Note",
|
||||
created_at: new Date(status.createdAt).toISOString(),
|
||||
id: status.id,
|
||||
author: this.getAuthor().getUri(),
|
||||
uri: this.getURI(),
|
||||
author: this.author.getUri(),
|
||||
uri: this.getUri(),
|
||||
content: {
|
||||
"text/html": {
|
||||
content: status.content,
|
||||
|
|
@ -774,8 +790,8 @@ export class Note {
|
|||
),
|
||||
is_sensitive: status.sensitive,
|
||||
mentions: status.mentions.map((mention) => mention.uri || ""),
|
||||
quotes: Note.getURI(status.quotingId) ?? undefined,
|
||||
replies_to: Note.getURI(status.replyId) ?? undefined,
|
||||
quotes: Note.getUri(status.quotingId) ?? undefined,
|
||||
replies_to: Note.getUri(status.replyId) ?? undefined,
|
||||
subject: status.spoilerText,
|
||||
visibility: status.visibility as
|
||||
| "public"
|
||||
|
|
@ -799,9 +815,9 @@ export class Note {
|
|||
|
||||
let currentStatus: Note = this;
|
||||
|
||||
while (currentStatus.getStatus().replyId) {
|
||||
while (currentStatus.data.replyId) {
|
||||
const parent = await Note.fromId(
|
||||
currentStatus.getStatus().replyId,
|
||||
currentStatus.data.replyId,
|
||||
fetcher?.id,
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -11,9 +11,20 @@ import {
|
|||
} from "drizzle-orm";
|
||||
import { db } from "~/drizzle/db";
|
||||
import { RoleToUsers, Roles } from "~/drizzle/schema";
|
||||
import { BaseInterface } from "./base";
|
||||
|
||||
export class Role {
|
||||
private constructor(private role: InferSelectModel<typeof Roles>) {}
|
||||
export type RoleType = InferSelectModel<typeof Roles>;
|
||||
|
||||
export class Role extends BaseInterface<typeof Roles> {
|
||||
async reload(): Promise<void> {
|
||||
const reloaded = await Role.fromId(this.data.id);
|
||||
|
||||
if (!reloaded) {
|
||||
throw new Error("Failed to reload role");
|
||||
}
|
||||
|
||||
this.data = reloaded.data;
|
||||
}
|
||||
|
||||
public static fromRole(role: InferSelectModel<typeof Roles>) {
|
||||
return new Role(role);
|
||||
|
|
@ -104,26 +115,44 @@ export class Role {
|
|||
return found.map((s) => new Role(s));
|
||||
}
|
||||
|
||||
public async save(
|
||||
role: Partial<InferSelectModel<typeof Roles>> = this.role,
|
||||
) {
|
||||
return new Role(
|
||||
(
|
||||
await db
|
||||
.update(Roles)
|
||||
.set(role)
|
||||
.where(eq(Roles.id, this.id))
|
||||
.returning()
|
||||
)[0],
|
||||
);
|
||||
async update(newRole: Partial<RoleType>): Promise<RoleType> {
|
||||
await db.update(Roles).set(newRole).where(eq(Roles.id, this.id));
|
||||
|
||||
const updated = await Role.fromId(this.data.id);
|
||||
|
||||
if (!updated) {
|
||||
throw new Error("Failed to update role");
|
||||
}
|
||||
|
||||
return updated.data;
|
||||
}
|
||||
|
||||
public async delete() {
|
||||
await db.delete(Roles).where(eq(Roles.id, this.id));
|
||||
async save(): Promise<RoleType> {
|
||||
return this.update(this.data);
|
||||
}
|
||||
|
||||
public static async new(role: InferInsertModel<typeof Roles>) {
|
||||
return new Role((await db.insert(Roles).values(role).returning())[0]);
|
||||
async delete(ids: string[]): Promise<void>;
|
||||
async delete(): Promise<void>;
|
||||
async delete(ids?: unknown): Promise<void> {
|
||||
if (Array.isArray(ids)) {
|
||||
await db.delete(Roles).where(inArray(Roles.id, ids));
|
||||
} else {
|
||||
await db.delete(Roles).where(eq(Roles.id, this.id));
|
||||
}
|
||||
}
|
||||
|
||||
public static async insert(
|
||||
data: InferInsertModel<typeof Roles>,
|
||||
): Promise<Role> {
|
||||
const inserted = (await db.insert(Roles).values(data).returning())[0];
|
||||
|
||||
const role = await Role.fromId(inserted.id);
|
||||
|
||||
if (!role) {
|
||||
throw new Error("Failed to insert role");
|
||||
}
|
||||
|
||||
return role;
|
||||
}
|
||||
|
||||
public async linkUser(userId: string) {
|
||||
|
|
@ -145,22 +174,18 @@ export class Role {
|
|||
}
|
||||
|
||||
get id() {
|
||||
return this.role.id;
|
||||
}
|
||||
|
||||
public getRole() {
|
||||
return this.role;
|
||||
return this.data.id;
|
||||
}
|
||||
|
||||
public toAPI() {
|
||||
return {
|
||||
id: this.id,
|
||||
name: this.role.name,
|
||||
permissions: this.role.permissions,
|
||||
priority: this.role.priority,
|
||||
description: this.role.description,
|
||||
visible: this.role.visible,
|
||||
icon: proxyUrl(this.role.icon),
|
||||
name: this.data.name,
|
||||
permissions: this.data.permissions,
|
||||
priority: this.data.priority,
|
||||
description: this.data.description,
|
||||
visible: this.data.visible,
|
||||
icon: proxyUrl(this.data.icon),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -74,23 +74,20 @@ export class Timeline {
|
|||
switch (this.type) {
|
||||
case TimelineType.NOTE: {
|
||||
const objectBefore = await Note.fromSql(
|
||||
gt(Notes.id, notes[0].getStatus().id),
|
||||
gt(Notes.id, notes[0].data.id),
|
||||
);
|
||||
|
||||
if (objectBefore) {
|
||||
linkHeader.push(
|
||||
`<${urlWithoutQuery}?limit=${limit ?? 20}&min_id=${
|
||||
notes[0].getStatus().id
|
||||
notes[0].data.id
|
||||
}>; rel="prev"`,
|
||||
);
|
||||
}
|
||||
|
||||
if (notes.length >= (limit ?? 20)) {
|
||||
const objectAfter = await Note.fromSql(
|
||||
gt(
|
||||
Notes.id,
|
||||
notes[notes.length - 1].getStatus().id,
|
||||
),
|
||||
gt(Notes.id, notes[notes.length - 1].data.id),
|
||||
);
|
||||
|
||||
if (objectAfter) {
|
||||
|
|
@ -98,7 +95,7 @@ export class Timeline {
|
|||
`<${urlWithoutQuery}?limit=${
|
||||
limit ?? 20
|
||||
}&max_id=${
|
||||
notes[notes.length - 1].getStatus().id
|
||||
notes[notes.length - 1].data.id
|
||||
}>; rel="next"`,
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -42,14 +42,23 @@ import {
|
|||
import { type Config, config } from "~/packages/config-manager";
|
||||
import type { Account as APIAccount } from "~/types/mastodon/account";
|
||||
import type { Mention as APIMention } from "~/types/mastodon/mention";
|
||||
import { BaseInterface } from "./base";
|
||||
import type { Note } from "./note";
|
||||
import { Role } from "./role";
|
||||
|
||||
/**
|
||||
* Gives helpers to fetch users from database in a nice format
|
||||
*/
|
||||
export class User {
|
||||
constructor(private user: UserWithRelations) {}
|
||||
export class User extends BaseInterface<typeof Users, UserWithRelations> {
|
||||
async reload(): Promise<void> {
|
||||
const reloaded = await User.fromId(this.data.id);
|
||||
|
||||
if (!reloaded) {
|
||||
throw new Error("Failed to reload user");
|
||||
}
|
||||
|
||||
this.data = reloaded.data;
|
||||
}
|
||||
|
||||
static async fromId(id: string | null): Promise<User | null> {
|
||||
if (!id) return null;
|
||||
|
|
@ -93,15 +102,11 @@ export class User {
|
|||
}
|
||||
|
||||
get id() {
|
||||
return this.user.id;
|
||||
}
|
||||
|
||||
getUser() {
|
||||
return this.user;
|
||||
return this.data.id;
|
||||
}
|
||||
|
||||
isLocal() {
|
||||
return this.user.instanceId === null;
|
||||
return this.data.instanceId === null;
|
||||
}
|
||||
|
||||
isRemote() {
|
||||
|
|
@ -110,8 +115,8 @@ export class User {
|
|||
|
||||
getUri() {
|
||||
return (
|
||||
this.user.uri ||
|
||||
new URL(`/users/${this.user.id}`, config.http.base_url).toString()
|
||||
this.data.uri ||
|
||||
new URL(`/users/${this.data.id}`, config.http.base_url).toString()
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -125,12 +130,12 @@ export class User {
|
|||
|
||||
public getAllPermissions() {
|
||||
return (
|
||||
this.user.roles
|
||||
this.data.roles
|
||||
.flatMap((role) => role.permissions)
|
||||
// Add default permissions
|
||||
.concat(config.permissions.default)
|
||||
// If admin, add admin permissions
|
||||
.concat(this.user.isAdmin ? config.permissions.admin : [])
|
||||
.concat(this.data.isAdmin ? config.permissions.admin : [])
|
||||
.reduce((acc, permission) => {
|
||||
if (!acc.includes(permission)) acc.push(permission);
|
||||
return acc;
|
||||
|
|
@ -169,10 +174,14 @@ export class User {
|
|||
)[0].count;
|
||||
}
|
||||
|
||||
async delete() {
|
||||
return (
|
||||
await db.delete(Users).where(eq(Users.id, this.id)).returning()
|
||||
)[0];
|
||||
async delete(ids: string[]): Promise<void>;
|
||||
async delete(): Promise<void>;
|
||||
async delete(ids?: unknown): Promise<void> {
|
||||
if (Array.isArray(ids)) {
|
||||
await db.delete(Users).where(inArray(Users.id, ids));
|
||||
} else {
|
||||
await db.delete(Users).where(eq(Users.id, this.id));
|
||||
}
|
||||
}
|
||||
|
||||
async resetPassword() {
|
||||
|
|
@ -211,17 +220,8 @@ export class User {
|
|||
)[0];
|
||||
}
|
||||
|
||||
async save() {
|
||||
return (
|
||||
await db
|
||||
.update(Users)
|
||||
.set({
|
||||
...this.user,
|
||||
updatedAt: new Date().toISOString(),
|
||||
})
|
||||
.where(eq(Users.id, this.id))
|
||||
.returning()
|
||||
)[0];
|
||||
async save(): Promise<UserWithRelations> {
|
||||
return this.update(this.data);
|
||||
}
|
||||
|
||||
async updateFromRemote() {
|
||||
|
|
@ -237,7 +237,7 @@ export class User {
|
|||
throw new Error("User not found after update");
|
||||
}
|
||||
|
||||
this.user = updated.getUser();
|
||||
this.data = updated.data;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
|
@ -405,12 +405,12 @@ export class User {
|
|||
* @returns The raw URL for the user's avatar
|
||||
*/
|
||||
getAvatarUrl(config: Config) {
|
||||
if (!this.user.avatar)
|
||||
if (!this.data.avatar)
|
||||
return (
|
||||
config.defaults.avatar ||
|
||||
`https://api.dicebear.com/8.x/${config.defaults.placeholder_style}/svg?seed=${this.user.username}`
|
||||
`https://api.dicebear.com/8.x/${config.defaults.placeholder_style}/svg?seed=${this.data.username}`
|
||||
);
|
||||
return this.user.avatar;
|
||||
return this.data.avatar;
|
||||
}
|
||||
|
||||
static async generateKeys() {
|
||||
|
|
@ -494,57 +494,50 @@ export class User {
|
|||
* @returns The raw URL for the user's header
|
||||
*/
|
||||
getHeaderUrl(config: Config) {
|
||||
if (!this.user.header) return config.defaults.header || "";
|
||||
return this.user.header;
|
||||
if (!this.data.header) return config.defaults.header || "";
|
||||
return this.data.header;
|
||||
}
|
||||
|
||||
getAcct() {
|
||||
return this.isLocal()
|
||||
? this.user.username
|
||||
: `${this.user.username}@${this.user.instance?.baseUrl}`;
|
||||
? this.data.username
|
||||
: `${this.data.username}@${this.data.instance?.baseUrl}`;
|
||||
}
|
||||
|
||||
static getAcct(isLocal: boolean, username: string, baseUrl?: string) {
|
||||
return isLocal ? username : `${username}@${baseUrl}`;
|
||||
}
|
||||
|
||||
async update(data: Partial<typeof Users.$inferSelect>) {
|
||||
const updated = (
|
||||
await db
|
||||
.update(Users)
|
||||
.set({
|
||||
...data,
|
||||
updatedAt: new Date().toISOString(),
|
||||
})
|
||||
.where(eq(Users.id, this.id))
|
||||
.returning()
|
||||
)[0];
|
||||
async update(
|
||||
newUser: Partial<UserWithRelations>,
|
||||
): Promise<UserWithRelations> {
|
||||
await db.update(Users).set(newUser).where(eq(Users.id, this.id));
|
||||
|
||||
const newUser = await User.fromId(updated.id);
|
||||
const updated = await User.fromId(this.data.id);
|
||||
|
||||
if (!newUser) throw new Error("User not found after update");
|
||||
|
||||
this.user = newUser.getUser();
|
||||
if (!updated) {
|
||||
throw new Error("Failed to update user");
|
||||
}
|
||||
|
||||
// If something important is updated, federate it
|
||||
if (
|
||||
data.username ||
|
||||
data.displayName ||
|
||||
data.note ||
|
||||
data.avatar ||
|
||||
data.header ||
|
||||
data.fields ||
|
||||
data.publicKey ||
|
||||
data.isAdmin ||
|
||||
data.isBot ||
|
||||
data.isLocked ||
|
||||
data.endpoints ||
|
||||
data.isDiscoverable
|
||||
newUser.username ||
|
||||
newUser.displayName ||
|
||||
newUser.note ||
|
||||
newUser.avatar ||
|
||||
newUser.header ||
|
||||
newUser.fields ||
|
||||
newUser.publicKey ||
|
||||
newUser.isAdmin ||
|
||||
newUser.isBot ||
|
||||
newUser.isLocked ||
|
||||
newUser.endpoints ||
|
||||
newUser.isDiscoverable
|
||||
) {
|
||||
await this.federateToFollowers(this.toLysand());
|
||||
}
|
||||
|
||||
return this;
|
||||
return updated.data;
|
||||
}
|
||||
|
||||
async federateToFollowers(object: typeof EntityValidator.$Entity) {
|
||||
|
|
@ -569,7 +562,7 @@ export class User {
|
|||
}
|
||||
|
||||
toAPI(isOwnAccount = false): APIAccount {
|
||||
const user = this.getUser();
|
||||
const user = this.data;
|
||||
return {
|
||||
id: user.id,
|
||||
username: user.username,
|
||||
|
|
@ -642,7 +635,7 @@ export class User {
|
|||
throw new Error("Cannot convert remote user to Lysand format");
|
||||
}
|
||||
|
||||
const user = this.getUser();
|
||||
const user = this.data;
|
||||
|
||||
return {
|
||||
id: user.id,
|
||||
|
|
@ -709,7 +702,7 @@ export class User {
|
|||
toMention(): APIMention {
|
||||
return {
|
||||
url: this.getUri(),
|
||||
username: this.getUser().username,
|
||||
username: this.data.username,
|
||||
acct: this.getAcct(),
|
||||
id: this.id,
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue