mirror of
https://iceshrimp.dev/limepotato/jormungandr-bite.git
synced 2024-11-25 03:17:38 -07:00
[backend/web-api] Add basic timeline endpoint
This commit is contained in:
parent
8134e92284
commit
ba76c5e67b
6 changed files with 77 additions and 13 deletions
|
@ -15,7 +15,7 @@ import { secureRndstr } from "@/misc/secure-rndstr.js";
|
|||
@Controller('/auth')
|
||||
export class AuthController {
|
||||
@Get('/')
|
||||
async getAuth(
|
||||
async getAuthStatus(
|
||||
@CurrentUser() me: ILocalUser | null,
|
||||
@CurrentSession() session: Session | null,
|
||||
): Promise<AuthResponse> {
|
||||
|
@ -30,15 +30,15 @@ export class AuthController {
|
|||
@Post('/')
|
||||
@Flow([RatelimitRouteMiddleware("auth", 10, 60000, true)])
|
||||
async login(@Body({ required: true }) request: AuthRequest): Promise<AuthResponse> {
|
||||
if (request.username == null || request.password == null) throw badRequest();
|
||||
if (request.username == null || request.password == null) throw badRequest("Missing username or password");
|
||||
|
||||
const user = await Users.findOneBy({ usernameLower: request.username.toLowerCase(), host: IsNull() });
|
||||
if (!user) throw unauthorized();
|
||||
if (!user) throw unauthorized("Invalid username or password");
|
||||
|
||||
const profile = await UserProfiles.findOneBy( { userId: user.id });
|
||||
if (!profile || profile.password == null) throw unauthorized();
|
||||
if (!profile || profile.password == null) throw unauthorized("Invalid username or password");
|
||||
|
||||
if (!await comparePassword(request.password, profile.password)) throw unauthorized();
|
||||
if (!await comparePassword(request.password, profile.password)) throw unauthorized("Invalid username or password");
|
||||
|
||||
const result = await Sessions.insert({
|
||||
id: genId(),
|
||||
|
@ -50,6 +50,6 @@ export class AuthController {
|
|||
|
||||
const session = await Sessions.findOneByOrFail(result.identifiers[0]);
|
||||
|
||||
return this.getAuth(user as ILocalUser, session);
|
||||
return this.getAuthStatus(user as ILocalUser, session);
|
||||
}
|
||||
}
|
||||
|
|
20
packages/backend/src/server/api/web/controllers/timeline.ts
Normal file
20
packages/backend/src/server/api/web/controllers/timeline.ts
Normal file
|
@ -0,0 +1,20 @@
|
|||
import { Controller, CurrentUser, Flow, Get, Params, Query } from "@iceshrimp/koa-openapi";
|
||||
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";
|
||||
import { TimelineHandler } from "@/server/api/web/handlers/timeline.js";
|
||||
import { AuthorizationMiddleware } from "@/server/api/web/middleware/auth.js";
|
||||
|
||||
@Controller('/timeline')
|
||||
export class TimelineController {
|
||||
@Get('/home')
|
||||
@Flow([AuthorizationMiddleware()])
|
||||
async getHomeTimeline(
|
||||
@CurrentUser() me: ILocalUser,
|
||||
@Query('limit') limit: number = 20,
|
||||
@Query('replies') replies: boolean = true,
|
||||
): Promise<TimelineResponse> {
|
||||
return TimelineHandler.getHomeTimeline(me, limit, replies);
|
||||
}
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
import { ILocalUser } from "@/models/entities/user.js";
|
||||
import { NoteResponse } from "@/server/api/web/entities/note.js";
|
||||
import { Notes } from "@/models/index.js";
|
||||
import { Boom, notFound, internal } from "@hapi/boom";
|
||||
import { Boom, 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";
|
||||
|
|
43
packages/backend/src/server/api/web/handlers/timeline.ts
Normal file
43
packages/backend/src/server/api/web/handlers/timeline.ts
Normal file
|
@ -0,0 +1,43 @@
|
|||
import { TimelineResponse } from "@/server/api/web/entities/note.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, User } from "@/models/entities/user.js";
|
||||
import { notFound } from "@hapi/boom";
|
||||
import { NoteHandler } from "@/server/api/web/handlers/note.js";
|
||||
import { generateFollowingQuery } from "@/server/api/common/generate-following-query.js";
|
||||
import { generateListQuery } from "@/server/api/common/generate-list-query.js";
|
||||
import { generateChannelQuery } from "@/server/api/common/generate-channel-query.js";
|
||||
import { generateRepliesQuery } from "@/server/api/common/generate-replies-query.js";
|
||||
import { generateMutedUserRenotesQueryForNotes } from "@/server/api/common/generated-muted-renote-query.js";
|
||||
|
||||
export class TimelineHandler {
|
||||
public static async getHomeTimeline(me: ILocalUser, limit: number, replies: boolean): Promise<TimelineResponse> {
|
||||
const query = makePaginationQuery(Notes.createQueryBuilder('note'))
|
||||
.innerJoinAndSelect("note.user", "user")
|
||||
.leftJoinAndSelect("note.reply", "reply")
|
||||
.leftJoinAndSelect("note.renote", "renote")
|
||||
.leftJoinAndSelect("reply.user", "replyUser")
|
||||
.leftJoinAndSelect("renote.user", "renoteUser");
|
||||
|
||||
await generateFollowingQuery(query, me);
|
||||
generateListQuery(query, me);
|
||||
generateChannelQuery(query, me);
|
||||
generateRepliesQuery(query, replies, me);
|
||||
generateVisibilityQuery(query, me);
|
||||
generateMutedUserQuery(query, me);
|
||||
generateBlockedUserQuery(query, me);
|
||||
generateMutedUserRenotesQueryForNotes(query, me);
|
||||
|
||||
query.andWhere("note.visibility != 'hidden'");
|
||||
|
||||
const result = query.take(Math.min(limit, 100)).getMany();
|
||||
return {
|
||||
notes: await NoteHandler.encodeMany(await result, me),
|
||||
limit: limit
|
||||
}
|
||||
}
|
||||
}
|
|
@ -8,6 +8,7 @@ import { ErrorHandlingMiddleware } from "@/server/api/web/middleware/error-handl
|
|||
import { AuthController } from "@/server/api/web/controllers/auth.js";
|
||||
import { NoteController } from "@/server/api/web/controllers/note.js";
|
||||
import { WebContext, WebRouter } from "@/server/api/web/misc/koa.js";
|
||||
import { TimelineController } from "@/server/api/web/controllers/timeline.js";
|
||||
|
||||
export class WebAPI {
|
||||
private readonly router: WebRouter;
|
||||
|
@ -26,6 +27,7 @@ export class WebAPI {
|
|||
UserController,
|
||||
NoteController,
|
||||
AuthController,
|
||||
TimelineController,
|
||||
],
|
||||
flow: [
|
||||
AuthenticationMiddleware,
|
||||
|
|
|
@ -3,6 +3,7 @@ import { Next } from "koa";
|
|||
import { Sessions } from "@/models/index.js";
|
||||
import { Session } from "@/models/entities/session.js";
|
||||
import { ILocalUser } from "@/models/entities/user.js";
|
||||
import { unauthorized } from "@hapi/boom";
|
||||
|
||||
export const AuthenticationMiddleware: WebMiddleware = async (ctx: WebContext, next: Next) => {
|
||||
const session = await authenticate(ctx.headers.authorization);
|
||||
|
@ -12,13 +13,11 @@ export const AuthenticationMiddleware: WebMiddleware = async (ctx: WebContext, n
|
|||
await next();
|
||||
}
|
||||
|
||||
export function AuthorizationMiddleware(required: boolean, scopes: string[] = []): WebMiddleware {
|
||||
export function AuthorizationMiddleware(admin: boolean = false): WebMiddleware {
|
||||
return async (ctx: WebContext, next: Next) => {
|
||||
try {
|
||||
if (required && !ctx.state.session?.active) {
|
||||
throw new Error(); //FIXME
|
||||
if (!ctx.state.session?.active || (admin && !ctx.state.session?.user.isAdmin)) {
|
||||
throw unauthorized("This method requires an authenticated user");
|
||||
}
|
||||
} catch {}
|
||||
|
||||
await next();
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue