[backend] More Web API rewrite preparations

This commit is contained in:
Laura Hausmann 2023-12-11 20:34:23 +01:00
parent 71d171a953
commit b9c86d0d4c
No known key found for this signature in database
GPG Key ID: D044E84C5BE01605
8 changed files with 62 additions and 42 deletions

View File

@ -9,6 +9,6 @@ export class NoteController {
@CurrentUser() me: ILocalUser | null, @CurrentUser() me: ILocalUser | null,
@Params('id') id: string, @Params('id') id: string,
) { ) {
NoteHandler.getNote(me, id); NoteHandler.getNoteOrFail(me, id);
} }
} }

View File

@ -2,6 +2,7 @@ import { Controller, Get, CurrentUser, Params, } from "@iceshrimp/koa-openapi";
import type { ILocalUser } from "@/models/entities/user.js"; import type { ILocalUser } from "@/models/entities/user.js";
import { NoteHandler } from "@/server/api/web/handlers/note.js"; import { NoteHandler } from "@/server/api/web/handlers/note.js";
import { NoteResponse } from "@/server/api/web/entities/note.js"; import { NoteResponse } from "@/server/api/web/entities/note.js";
import { notFound } from "@hapi/boom";
@Controller('/note') @Controller('/note')
export class NoteController { export class NoteController {
@ -10,6 +11,7 @@ export class NoteController {
@CurrentUser() me: ILocalUser | null, @CurrentUser() me: ILocalUser | null,
@Params('id') id: string, @Params('id') id: string,
): Promise<NoteResponse> { ): Promise<NoteResponse> {
return NoteHandler.getNote(me, id); return NoteHandler.getNoteOrFail(id, notFound("No such note"))
.then(note => NoteHandler.encodeOrFail(note, me));
} }
} }

View File

@ -1,5 +1,5 @@
import { Controller, CurrentUser, Get, Params, Query } from "@iceshrimp/koa-openapi"; 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 { TimelineResponse } from "@/server/api/web/entities/note.js";
import type { ILocalUser } from "@/models/entities/user.js"; import type { ILocalUser } from "@/models/entities/user.js";
import { UserHandler } from "@/server/api/web/handlers/user.js"; import { UserHandler } from "@/server/api/web/handlers/user.js";
@ -11,10 +11,8 @@ export class UserController {
@CurrentUser() me: ILocalUser | null, @CurrentUser() me: ILocalUser | null,
@Params('id') id: string, @Params('id') id: string,
@Query('detail') detail: boolean @Query('detail') detail: boolean
): Promise<UserResponse | UserDetailedResponse> { ): Promise<UserResponse> {
return detail return UserHandler.getUser(me, id);
? UserHandler.getUser(me, id)
: UserHandler.getUserDetailed(me, id);
} }
@Get('/:id/notes') @Get('/:id/notes')

View File

@ -1,2 +0,0 @@
export namespace WebEntities {}

View File

@ -1,13 +1,15 @@
import { Note } from "@/models/entities/note.js"; import { Note } from "@/models/entities/note.js";
import { UserResponse } from "@/server/api/web/entities/user.js";
namespace WebEntities { export type NoteResponse = {
export type NoteResponse = { id: Note["id"];
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 = { pagination: {}; //TODO
notes: NoteResponse[], };
};
}

View File

@ -4,8 +4,3 @@ export type UserResponse = {
avatarUrl?: string; avatarUrl?: string;
bannerUrl?: string; bannerUrl?: string;
} }
export type UserDetailedResponse = UserResponse & {
followers: number;
following: number;
}

View File

@ -1,17 +1,41 @@
import { ILocalUser } from "@/models/entities/user.js"; import { ILocalUser } from "@/models/entities/user.js";
import { NoteResponse } from "@/server/api/web/entities/note.js"; import { NoteResponse } from "@/server/api/web/entities/note.js";
import { Notes } from "@/models/index.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 { export class NoteHandler {
static async getNote(me: ILocalUser | null, id: string): Promise<NoteResponse> { static async getNoteOrFail(id: string, error: Boom = internal('No such note')): Promise<Note> {
const note = await Notes.findOneBy({ id }); const note = await this.getNote(id);
if (!note) throw notFound('No such user'); if (!note) throw error;
return note; return note;
} }
static async encode(me: ILocalUser | null, id: string): Promise<NoteResponse | null> { static async getNote(id: string): Promise<Note | null> {
return Notes.findOneBy({ id });
}
static async encode(note: Note, me: ILocalUser | null, recurse: number = 2): Promise<NoteResponse | null> {
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<NoteResponse> {
const result = await this.encode(note, me);
if (!result) throw error;
return result;
}
static async encodeMany(notes: Note[], me: ILocalUser | null): Promise<NoteResponse[]> {
return Promise.all(notes.map(n => this.encodeOrFail(n, me)));
} }
} }

View File

@ -1,12 +1,13 @@
import { TimelineResponse } from "@/server/api/web/entities/note.js"; 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 { Notes, UserProfiles, Users } from "@/models/index.js";
import { makePaginationQuery } from "@/server/api/common/make-pagination-query.js"; import { makePaginationQuery } from "@/server/api/common/make-pagination-query.js";
import { generateVisibilityQuery } from "@/server/api/common/generate-visibility-query.js"; import { generateVisibilityQuery } from "@/server/api/common/generate-visibility-query.js";
import { generateMutedUserQuery } from "@/server/api/common/generate-muted-user-query.js"; import { generateMutedUserQuery } from "@/server/api/common/generate-muted-user-query.js";
import { generateBlockedUserQuery } from "@/server/api/common/generate-block-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 { notFound } from "@hapi/boom";
import { NoteHandler } from "@/server/api/web/handlers/note.js";
export class UserHandler { export class UserHandler {
public static async getUserNotes(me: ILocalUser | null, id: string, limit: number, replies: boolean): Promise<TimelineResponse> { public static async getUserNotes(me: ILocalUser | null, id: string, limit: number, replies: boolean): Promise<TimelineResponse> {
@ -31,12 +32,21 @@ export class UserHandler {
query.andWhere("note.replyId IS NULL"); 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<UserResponse> { public static async getUser(me: ILocalUser | null, id: string): Promise<UserResponse> {
const user = await Users.findOneBy({ id }); const user = await Users.findOneBy({ id });
if (!user) throw notFound('No such user'); if (!user) throw notFound('No such user');
return this.encode(user, me);
}
public static async encode(user: User, me: ILocalUser | null): Promise<UserResponse> {
return { return {
id: user.id, id: user.id,
username: user.username, username: user.username,
@ -44,13 +54,4 @@ export class UserHandler {
bannerUrl: user.bannerUrl ?? undefined, bannerUrl: user.bannerUrl ?? undefined,
}; };
} }
public static async getUserDetailed(me: ILocalUser | null, id: string): Promise<UserDetailedResponse> {
const profile = await UserProfiles.findOneBy({ userId: id });
return {
followers: 0,
following: 0,
...await this.getUser(me, id),
}
}
} }