diff --git a/packages/backend/src/server/api/web/controllers/_template.ts b/packages/backend/src/server/api/web/controllers/_template.ts index 1015b0140..555e99242 100644 --- a/packages/backend/src/server/api/web/controllers/_template.ts +++ b/packages/backend/src/server/api/web/controllers/_template.ts @@ -9,6 +9,6 @@ export class NoteController { @CurrentUser() me: ILocalUser | null, @Params('id') id: string, ) { - NoteHandler.getNote(me, id); + NoteHandler.getNoteOrFail(me, id); } } diff --git a/packages/backend/src/server/api/web/controllers/note.ts b/packages/backend/src/server/api/web/controllers/note.ts index e3c08239e..ea1f425a2 100644 --- a/packages/backend/src/server/api/web/controllers/note.ts +++ b/packages/backend/src/server/api/web/controllers/note.ts @@ -2,6 +2,7 @@ import { Controller, Get, CurrentUser, Params, } from "@iceshrimp/koa-openapi"; import type { ILocalUser } from "@/models/entities/user.js"; import { NoteHandler } from "@/server/api/web/handlers/note.js"; import { NoteResponse } from "@/server/api/web/entities/note.js"; +import { notFound } from "@hapi/boom"; @Controller('/note') export class NoteController { @@ -10,6 +11,7 @@ export class NoteController { @CurrentUser() me: ILocalUser | null, @Params('id') id: string, ): Promise { - return NoteHandler.getNote(me, id); + return NoteHandler.getNoteOrFail(id, notFound("No such note")) + .then(note => NoteHandler.encodeOrFail(note, me)); } } diff --git a/packages/backend/src/server/api/web/controllers/user.ts b/packages/backend/src/server/api/web/controllers/user.ts index d294001ea..bf5e4c2a4 100644 --- a/packages/backend/src/server/api/web/controllers/user.ts +++ b/packages/backend/src/server/api/web/controllers/user.ts @@ -1,5 +1,5 @@ import { Controller, CurrentUser, Get, Params, Query } from "@iceshrimp/koa-openapi"; -import { UserDetailedResponse, UserResponse } from "@/server/api/web/entities/user.js"; +import { UserResponse } from "@/server/api/web/entities/user.js"; import { TimelineResponse } from "@/server/api/web/entities/note.js"; import type { ILocalUser } from "@/models/entities/user.js"; import { UserHandler } from "@/server/api/web/handlers/user.js"; @@ -11,10 +11,8 @@ export class UserController { @CurrentUser() me: ILocalUser | null, @Params('id') id: string, @Query('detail') detail: boolean - ): Promise { - return detail - ? UserHandler.getUser(me, id) - : UserHandler.getUserDetailed(me, id); + ): Promise { + return UserHandler.getUser(me, id); } @Get('/:id/notes') diff --git a/packages/backend/src/server/api/web/entities/index.ts b/packages/backend/src/server/api/web/entities/index.ts deleted file mode 100644 index a44960c26..000000000 --- a/packages/backend/src/server/api/web/entities/index.ts +++ /dev/null @@ -1,2 +0,0 @@ - -export namespace WebEntities {} diff --git a/packages/backend/src/server/api/web/entities/note.ts b/packages/backend/src/server/api/web/entities/note.ts index 2eb658f1d..dcbca7820 100644 --- a/packages/backend/src/server/api/web/entities/note.ts +++ b/packages/backend/src/server/api/web/entities/note.ts @@ -1,13 +1,15 @@ import { Note } from "@/models/entities/note.js"; +import { UserResponse } from "@/server/api/web/entities/user.js"; -namespace WebEntities { - export type NoteResponse = { - id: Note["id"]; +export type NoteResponse = { + id: Note["id"]; + text: string | null; + user: UserResponse; + reply: NoteResponse | undefined | null; // Undefined if no record, null if not visible + renote: NoteResponse | undefined | null; // Undefined if no record, null if not visible +}; - }; - - export type TimelineResponse = { - notes: NoteResponse[], - - }; -} +export type TimelineResponse = { + notes: NoteResponse[]; + pagination: {}; //TODO +}; diff --git a/packages/backend/src/server/api/web/entities/user.ts b/packages/backend/src/server/api/web/entities/user.ts index 459efeeb1..333d2a819 100644 --- a/packages/backend/src/server/api/web/entities/user.ts +++ b/packages/backend/src/server/api/web/entities/user.ts @@ -4,8 +4,3 @@ export type UserResponse = { avatarUrl?: string; bannerUrl?: string; } - -export type UserDetailedResponse = UserResponse & { - followers: number; - following: number; -} diff --git a/packages/backend/src/server/api/web/handlers/note.ts b/packages/backend/src/server/api/web/handlers/note.ts index 8676acf65..d0870509b 100644 --- a/packages/backend/src/server/api/web/handlers/note.ts +++ b/packages/backend/src/server/api/web/handlers/note.ts @@ -1,17 +1,41 @@ import { ILocalUser } from "@/models/entities/user.js"; import { NoteResponse } from "@/server/api/web/entities/note.js"; import { Notes } from "@/models/index.js"; -import { notFound } from "@hapi/boom"; +import { Boom, notFound, internal } from "@hapi/boom"; +import { Note } from "@/models/entities/note.js"; +import { UserHandler } from "@/server/api/web/handlers/user.js"; +import isQuote from "@/misc/is-quote.js"; export class NoteHandler { - static async getNote(me: ILocalUser | null, id: string): Promise { - const note = await Notes.findOneBy({ id }); - if (!note) throw notFound('No such user'); - + static async getNoteOrFail(id: string, error: Boom = internal('No such note')): Promise { + const note = await this.getNote(id); + if (!note) throw error; return note; } - static async encode(me: ILocalUser | null, id: string): Promise { + static async getNote(id: string): Promise { + return Notes.findOneBy({ id }); + } + static async encode(note: Note, me: ILocalUser | null, recurse: number = 2): Promise { + if (!await Notes.isVisibleForMe(note, me?.id ?? null)) return null; + + return { + id: note.id, + text: note.text, + user: note.user ? await UserHandler.encode(note.user, me) : await UserHandler.getUser(me, note.userId), + renote: note.renoteId && recurse > 0 ? await this.encode(note.renote ?? await this.getNoteOrFail(note.renoteId), me, isQuote(note) ? --recurse : 0) : undefined, + reply: note.replyId && recurse > 0 ? await this.encode(note.renote ?? await this.getNoteOrFail(note.replyId), me, 0) : undefined, + }; + } + + static async encodeOrFail(note: Note, me: ILocalUser | null, error: Boom = internal("Cannot encode note not visible for user")): Promise { + const result = await this.encode(note, me); + if (!result) throw error; + return result; + } + + static async encodeMany(notes: Note[], me: ILocalUser | null): Promise { + return Promise.all(notes.map(n => this.encodeOrFail(n, me))); } } diff --git a/packages/backend/src/server/api/web/handlers/user.ts b/packages/backend/src/server/api/web/handlers/user.ts index 595f3bd9f..2b537a7b8 100644 --- a/packages/backend/src/server/api/web/handlers/user.ts +++ b/packages/backend/src/server/api/web/handlers/user.ts @@ -1,12 +1,13 @@ import { TimelineResponse } from "@/server/api/web/entities/note.js"; -import { UserDetailedResponse, UserResponse } from "@/server/api/web/entities/user.js"; +import { UserResponse } from "@/server/api/web/entities/user.js"; import { Notes, UserProfiles, Users } from "@/models/index.js"; import { makePaginationQuery } from "@/server/api/common/make-pagination-query.js"; import { generateVisibilityQuery } from "@/server/api/common/generate-visibility-query.js"; import { generateMutedUserQuery } from "@/server/api/common/generate-muted-user-query.js"; import { generateBlockedUserQuery } from "@/server/api/common/generate-block-query.js"; -import { ILocalUser } from "@/models/entities/user.js"; +import { ILocalUser, User } from "@/models/entities/user.js"; import { notFound } from "@hapi/boom"; +import { NoteHandler } from "@/server/api/web/handlers/note.js"; export class UserHandler { public static async getUserNotes(me: ILocalUser | null, id: string, limit: number, replies: boolean): Promise { @@ -31,12 +32,21 @@ export class UserHandler { query.andWhere("note.replyId IS NULL"); } - return query.take(Math.min(limit, 100)).getMany(); + const result = query.take(Math.min(limit, 100)).getMany(); + return { + notes: await NoteHandler.encodeMany(await result, me), + pagination: {}, + } } public static async getUser(me: ILocalUser | null, id: string): Promise { const user = await Users.findOneBy({ id }); if (!user) throw notFound('No such user'); + + return this.encode(user, me); + } + + public static async encode(user: User, me: ILocalUser | null): Promise { return { id: user.id, username: user.username, @@ -44,13 +54,4 @@ export class UserHandler { bannerUrl: user.bannerUrl ?? undefined, }; } - - public static async getUserDetailed(me: ILocalUser | null, id: string): Promise { - const profile = await UserProfiles.findOneBy({ userId: id }); - return { - followers: 0, - following: 0, - ...await this.getUser(me, id), - } - } }