mirror of
https://codeberg.org/yeentown/barkey.git
synced 2025-07-07 04:26:58 +00:00
improve compatibility with multipart/form-data mastodon API requests
This commit is contained in:
parent
1a19301c90
commit
cd4fbc851b
11 changed files with 202 additions and 241 deletions
|
@ -115,7 +115,6 @@
|
||||||
"deep-email-validator": "0.1.21",
|
"deep-email-validator": "0.1.21",
|
||||||
"fast-xml-parser": "4.4.1",
|
"fast-xml-parser": "4.4.1",
|
||||||
"fastify": "5.3.2",
|
"fastify": "5.3.2",
|
||||||
"fastify-multer": "^2.0.3",
|
|
||||||
"fastify-raw-body": "5.0.0",
|
"fastify-raw-body": "5.0.0",
|
||||||
"feed": "4.2.2",
|
"feed": "4.2.2",
|
||||||
"file-type": "19.6.0",
|
"file-type": "19.6.0",
|
||||||
|
|
|
@ -16,6 +16,7 @@ import { ApiTimelineMastodon } from '@/server/api/mastodon/endpoints/timeline.js
|
||||||
import { ApiAppsMastodon } from '@/server/api/mastodon/endpoints/apps.js';
|
import { ApiAppsMastodon } from '@/server/api/mastodon/endpoints/apps.js';
|
||||||
import { ApiInstanceMastodon } from '@/server/api/mastodon/endpoints/instance.js';
|
import { ApiInstanceMastodon } from '@/server/api/mastodon/endpoints/instance.js';
|
||||||
import { ApiStatusMastodon } from '@/server/api/mastodon/endpoints/status.js';
|
import { ApiStatusMastodon } from '@/server/api/mastodon/endpoints/status.js';
|
||||||
|
import { ServerUtilityService } from '@/server/ServerUtilityService.js';
|
||||||
import { ApiCallService } from './api/ApiCallService.js';
|
import { ApiCallService } from './api/ApiCallService.js';
|
||||||
import { FileServerService } from './FileServerService.js';
|
import { FileServerService } from './FileServerService.js';
|
||||||
import { HealthServerService } from './HealthServerService.js';
|
import { HealthServerService } from './HealthServerService.js';
|
||||||
|
@ -126,6 +127,7 @@ import { SigninWithPasskeyApiService } from './api/SigninWithPasskeyApiService.j
|
||||||
ApiSearchMastodon,
|
ApiSearchMastodon,
|
||||||
ApiStatusMastodon,
|
ApiStatusMastodon,
|
||||||
ApiTimelineMastodon,
|
ApiTimelineMastodon,
|
||||||
|
ServerUtilityService,
|
||||||
],
|
],
|
||||||
exports: [
|
exports: [
|
||||||
ServerService,
|
ServerService,
|
||||||
|
|
141
packages/backend/src/server/ServerUtilityService.ts
Normal file
141
packages/backend/src/server/ServerUtilityService.ts
Normal file
|
@ -0,0 +1,141 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: hazelnoot and other Sharkey contributors
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
import querystring from 'querystring';
|
||||||
|
import multipart from '@fastify/multipart';
|
||||||
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
|
import { FastifyInstance } from 'fastify';
|
||||||
|
import { DI } from '@/di-symbols.js';
|
||||||
|
import type { Config } from '@/config.js';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class ServerUtilityService {
|
||||||
|
constructor(
|
||||||
|
@Inject(DI.config)
|
||||||
|
private readonly config: Config,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public addMultipartFormDataContentType(fastify: FastifyInstance): void {
|
||||||
|
fastify.register(multipart, {
|
||||||
|
limits: {
|
||||||
|
fileSize: this.config.maxFileSize,
|
||||||
|
files: 1,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Default behavior saves files to memory - we don't want that!
|
||||||
|
// Store to temporary file instead, and copy the body fields while we're at it.
|
||||||
|
fastify.addHook<{ Body?: Record<string, string | string[] | undefined> }>('onRequest', async request => {
|
||||||
|
if (request.isMultipart()) {
|
||||||
|
const body = request.body ??= {};
|
||||||
|
|
||||||
|
// Save upload to temp directory.
|
||||||
|
// These are attached to request.savedRequestFiles
|
||||||
|
await request.saveRequestFiles();
|
||||||
|
|
||||||
|
// Copy fields to body
|
||||||
|
const formData = await request.formData();
|
||||||
|
formData.forEach((v, k) => {
|
||||||
|
// This can be string or File, and we handle files above.
|
||||||
|
if (typeof(v) === 'string') {
|
||||||
|
// This is just progressive conversion from undefined -> string -> string[]
|
||||||
|
if (body[k]) {
|
||||||
|
if (Array.isArray(body[k])) {
|
||||||
|
body[k].push(v);
|
||||||
|
} else {
|
||||||
|
body[k] = [body[k], v];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
body[k] = v;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public addFormUrlEncodedContentType(fastify: FastifyInstance) {
|
||||||
|
fastify.addContentTypeParser('application/x-www-form-urlencoded', (_, payload, done) => {
|
||||||
|
let body = '';
|
||||||
|
payload.on('data', (data) => {
|
||||||
|
body += data;
|
||||||
|
});
|
||||||
|
payload.on('end', () => {
|
||||||
|
try {
|
||||||
|
const parsed = querystring.parse(body);
|
||||||
|
done(null, parsed);
|
||||||
|
} catch (e) {
|
||||||
|
done(e as Error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
payload.on('error', done);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public addCORS(fastify: FastifyInstance) {
|
||||||
|
fastify.addHook('onRequest', (_, reply, done) => {
|
||||||
|
// Allow web-based clients to connect from other origins.
|
||||||
|
reply.header('Access-Control-Allow-Origin', '*');
|
||||||
|
|
||||||
|
// Mastodon uses all types of request methods.
|
||||||
|
reply.header('Access-Control-Allow-Methods', '*');
|
||||||
|
|
||||||
|
// Allow web-based clients to access Link header - required for mastodon pagination.
|
||||||
|
// https://stackoverflow.com/a/54928828
|
||||||
|
// https://docs.joinmastodon.org/api/guidelines/#pagination
|
||||||
|
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Access-Control-Expose-Headers
|
||||||
|
reply.header('Access-Control-Expose-Headers', 'Link');
|
||||||
|
|
||||||
|
// Cache to avoid extra pre-flight requests
|
||||||
|
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Access-Control-Max-Age
|
||||||
|
reply.header('Access-Control-Max-Age', 60 * 60 * 24); // 1 day in seconds
|
||||||
|
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public addFlattenedQueryType(fastify: FastifyInstance) {
|
||||||
|
// Remove trailing "[]" from query params
|
||||||
|
fastify.addHook<{ Querystring?: Record<string, string | string[] | undefined> }>('preValidation', (request, _reply, done) => {
|
||||||
|
if (!request.query || typeof(request.query) !== 'object') {
|
||||||
|
return done();
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const key of Object.keys(request.query)) {
|
||||||
|
if (!key.endsWith('[]')) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (request.query[key] == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const newKey = key.substring(0, key.length - 2);
|
||||||
|
const newValue = request.query[key];
|
||||||
|
const oldValue = request.query[newKey];
|
||||||
|
|
||||||
|
// Move the value to the correct key
|
||||||
|
if (oldValue != null) {
|
||||||
|
if (Array.isArray(oldValue)) {
|
||||||
|
// Works for both array and single values
|
||||||
|
request.query[newKey] = oldValue.concat(newValue);
|
||||||
|
} else if (Array.isArray(newValue)) {
|
||||||
|
// Preserve order
|
||||||
|
request.query[newKey] = [oldValue, ...newValue];
|
||||||
|
} else {
|
||||||
|
// Preserve order
|
||||||
|
request.query[newKey] = [oldValue, newValue];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
request.query[newKey] = newValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove the invalid key
|
||||||
|
delete request.query[key];
|
||||||
|
}
|
||||||
|
|
||||||
|
return done();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,12 +3,8 @@
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import querystring from 'querystring';
|
import { Injectable } from '@nestjs/common';
|
||||||
import multer from 'fastify-multer';
|
|
||||||
import { Inject, Injectable } from '@nestjs/common';
|
|
||||||
import { DI } from '@/di-symbols.js';
|
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
import type { Config } from '@/config.js';
|
|
||||||
import { getErrorData, getErrorStatus, MastodonLogger } from '@/server/api/mastodon/MastodonLogger.js';
|
import { getErrorData, getErrorStatus, MastodonLogger } from '@/server/api/mastodon/MastodonLogger.js';
|
||||||
import { MastodonClientService } from '@/server/api/mastodon/MastodonClientService.js';
|
import { MastodonClientService } from '@/server/api/mastodon/MastodonClientService.js';
|
||||||
import { ApiAccountMastodon } from '@/server/api/mastodon/endpoints/account.js';
|
import { ApiAccountMastodon } from '@/server/api/mastodon/endpoints/account.js';
|
||||||
|
@ -20,6 +16,7 @@ import { ApiNotificationsMastodon } from '@/server/api/mastodon/endpoints/notifi
|
||||||
import { ApiTimelineMastodon } from '@/server/api/mastodon/endpoints/timeline.js';
|
import { ApiTimelineMastodon } from '@/server/api/mastodon/endpoints/timeline.js';
|
||||||
import { ApiSearchMastodon } from '@/server/api/mastodon/endpoints/search.js';
|
import { ApiSearchMastodon } from '@/server/api/mastodon/endpoints/search.js';
|
||||||
import { ApiError } from '@/server/api/error.js';
|
import { ApiError } from '@/server/api/error.js';
|
||||||
|
import { ServerUtilityService } from '@/server/ServerUtilityService.js';
|
||||||
import { parseTimelineArgs, TimelineArgs, toBoolean } from './argsUtils.js';
|
import { parseTimelineArgs, TimelineArgs, toBoolean } from './argsUtils.js';
|
||||||
import { convertAnnouncement, convertAttachment, MastodonConverters, convertRelationship } from './MastodonConverters.js';
|
import { convertAnnouncement, convertAttachment, MastodonConverters, convertRelationship } from './MastodonConverters.js';
|
||||||
import type { Entity } from 'megalodon';
|
import type { Entity } from 'megalodon';
|
||||||
|
@ -28,9 +25,6 @@ import type { FastifyInstance, FastifyPluginOptions } from 'fastify';
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class MastodonApiServerService {
|
export class MastodonApiServerService {
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(DI.config)
|
|
||||||
private readonly config: Config,
|
|
||||||
|
|
||||||
private readonly mastoConverters: MastodonConverters,
|
private readonly mastoConverters: MastodonConverters,
|
||||||
private readonly logger: MastodonLogger,
|
private readonly logger: MastodonLogger,
|
||||||
private readonly clientService: MastodonClientService,
|
private readonly clientService: MastodonClientService,
|
||||||
|
@ -42,97 +36,15 @@ export class MastodonApiServerService {
|
||||||
private readonly apiSearchMastodon: ApiSearchMastodon,
|
private readonly apiSearchMastodon: ApiSearchMastodon,
|
||||||
private readonly apiStatusMastodon: ApiStatusMastodon,
|
private readonly apiStatusMastodon: ApiStatusMastodon,
|
||||||
private readonly apiTimelineMastodon: ApiTimelineMastodon,
|
private readonly apiTimelineMastodon: ApiTimelineMastodon,
|
||||||
|
private readonly serverUtilityService: ServerUtilityService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public createServer(fastify: FastifyInstance, _options: FastifyPluginOptions, done: (err?: Error) => void) {
|
public createServer(fastify: FastifyInstance, _options: FastifyPluginOptions, done: (err?: Error) => void) {
|
||||||
const upload = multer({
|
this.serverUtilityService.addMultipartFormDataContentType(fastify);
|
||||||
storage: multer.diskStorage({}),
|
this.serverUtilityService.addFormUrlEncodedContentType(fastify);
|
||||||
limits: {
|
this.serverUtilityService.addCORS(fastify);
|
||||||
fileSize: this.config.maxFileSize || 262144000,
|
this.serverUtilityService.addFlattenedQueryType(fastify);
|
||||||
files: 1,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
fastify.addHook('onRequest', (_, reply, done) => {
|
|
||||||
// Allow web-based clients to connect from other origins.
|
|
||||||
reply.header('Access-Control-Allow-Origin', '*');
|
|
||||||
|
|
||||||
// Mastodon uses all types of request methods.
|
|
||||||
reply.header('Access-Control-Allow-Methods', '*');
|
|
||||||
|
|
||||||
// Allow web-based clients to access Link header - required for mastodon pagination.
|
|
||||||
// https://stackoverflow.com/a/54928828
|
|
||||||
// https://docs.joinmastodon.org/api/guidelines/#pagination
|
|
||||||
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Access-Control-Expose-Headers
|
|
||||||
reply.header('Access-Control-Expose-Headers', 'Link');
|
|
||||||
|
|
||||||
// Cache to avoid extra pre-flight requests
|
|
||||||
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Access-Control-Max-Age
|
|
||||||
reply.header('Access-Control-Max-Age', 60 * 60 * 24); // 1 day in seconds
|
|
||||||
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
|
|
||||||
fastify.addContentTypeParser('application/x-www-form-urlencoded', (_, payload, done) => {
|
|
||||||
let body = '';
|
|
||||||
payload.on('data', (data) => {
|
|
||||||
body += data;
|
|
||||||
});
|
|
||||||
payload.on('end', () => {
|
|
||||||
try {
|
|
||||||
const parsed = querystring.parse(body);
|
|
||||||
done(null, parsed);
|
|
||||||
} catch (e) {
|
|
||||||
done(e as Error);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
payload.on('error', done);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Remove trailing "[]" from query params
|
|
||||||
fastify.addHook('preValidation', (request, _reply, done) => {
|
|
||||||
if (!request.query || typeof(request.query) !== 'object') {
|
|
||||||
return done();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Same object aliased with a different type
|
|
||||||
const query = request.query as Record<string, string | string[] | undefined>;
|
|
||||||
|
|
||||||
for (const key of Object.keys(query)) {
|
|
||||||
if (!key.endsWith('[]')) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (query[key] == null) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const newKey = key.substring(0, key.length - 2);
|
|
||||||
const newValue = query[key];
|
|
||||||
const oldValue = query[newKey];
|
|
||||||
|
|
||||||
// Move the value to the correct key
|
|
||||||
if (oldValue != null) {
|
|
||||||
if (Array.isArray(oldValue)) {
|
|
||||||
// Works for both array and single values
|
|
||||||
query[newKey] = oldValue.concat(newValue);
|
|
||||||
} else if (Array.isArray(newValue)) {
|
|
||||||
// Preserve order
|
|
||||||
query[newKey] = [oldValue, ...newValue];
|
|
||||||
} else {
|
|
||||||
// Preserve order
|
|
||||||
query[newKey] = [oldValue, newValue];
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
query[newKey] = newValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove the invalid key
|
|
||||||
delete query[key];
|
|
||||||
}
|
|
||||||
|
|
||||||
return done();
|
|
||||||
});
|
|
||||||
|
|
||||||
fastify.setErrorHandler((error, request, reply) => {
|
fastify.setErrorHandler((error, request, reply) => {
|
||||||
const data = getErrorData(error);
|
const data = getErrorData(error);
|
||||||
|
@ -143,14 +55,12 @@ export class MastodonApiServerService {
|
||||||
reply.code(status).send(data);
|
reply.code(status).send(data);
|
||||||
});
|
});
|
||||||
|
|
||||||
fastify.register(multer.contentParser);
|
|
||||||
|
|
||||||
// External endpoints
|
// External endpoints
|
||||||
this.apiAccountMastodon.register(fastify, upload);
|
this.apiAccountMastodon.register(fastify);
|
||||||
this.apiAppsMastodon.register(fastify, upload);
|
this.apiAppsMastodon.register(fastify);
|
||||||
this.apiFilterMastodon.register(fastify, upload);
|
this.apiFilterMastodon.register(fastify);
|
||||||
this.apiInstanceMastodon.register(fastify);
|
this.apiInstanceMastodon.register(fastify);
|
||||||
this.apiNotificationsMastodon.register(fastify, upload);
|
this.apiNotificationsMastodon.register(fastify);
|
||||||
this.apiSearchMastodon.register(fastify);
|
this.apiSearchMastodon.register(fastify);
|
||||||
this.apiStatusMastodon.register(fastify);
|
this.apiStatusMastodon.register(fastify);
|
||||||
this.apiTimelineMastodon.register(fastify);
|
this.apiTimelineMastodon.register(fastify);
|
||||||
|
@ -178,11 +88,10 @@ export class MastodonApiServerService {
|
||||||
reply.send(data.data);
|
reply.send(data.data);
|
||||||
});
|
});
|
||||||
|
|
||||||
fastify.post('/v1/media', { preHandler: upload.single('file') }, async (_request, reply) => {
|
fastify.post('/v1/media', async (_request, reply) => {
|
||||||
const multipartData = await _request.file();
|
const multipartData = _request.savedRequestFiles?.[0];
|
||||||
if (!multipartData) {
|
if (!multipartData) {
|
||||||
reply.code(401).send({ error: 'No image' });
|
return reply.code(400).send({ error: 'BAD_REQUEST', error_description: 'No image' });
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const client = this.clientService.getClient(_request);
|
const client = this.clientService.getClient(_request);
|
||||||
|
@ -192,11 +101,10 @@ export class MastodonApiServerService {
|
||||||
reply.send(response);
|
reply.send(response);
|
||||||
});
|
});
|
||||||
|
|
||||||
fastify.post<{ Body: { description?: string; focus?: string } }>('/v2/media', { preHandler: upload.single('file') }, async (_request, reply) => {
|
fastify.post<{ Body: { description?: string; focus?: string } }>('/v2/media', async (_request, reply) => {
|
||||||
const multipartData = await _request.file();
|
const multipartData = _request.savedRequestFiles?.[0];
|
||||||
if (!multipartData) {
|
if (!multipartData) {
|
||||||
reply.code(401).send({ error: 'No image' });
|
return reply.code(400).send({ error: 'BAD_REQUEST', error_description: 'No image' });
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const client = this.clientService.getClient(_request);
|
const client = this.clientService.getClient(_request);
|
||||||
|
@ -294,7 +202,7 @@ export class MastodonApiServerService {
|
||||||
reply.send(response);
|
reply.send(response);
|
||||||
});
|
});
|
||||||
|
|
||||||
fastify.post<{ Querystring: TimelineArgs, Params: { id?: string } }>('/v1/follow_requests/:id/authorize', { preHandler: upload.single('none') }, async (_request, reply) => {
|
fastify.post<{ Params: { id?: string } }>('/v1/follow_requests/:id/authorize', async (_request, reply) => {
|
||||||
if (!_request.params.id) return reply.code(400).send({ error: 'BAD_REQUEST', error_description: 'Missing required parameter "id"' });
|
if (!_request.params.id) return reply.code(400).send({ error: 'BAD_REQUEST', error_description: 'Missing required parameter "id"' });
|
||||||
|
|
||||||
const client = this.clientService.getClient(_request);
|
const client = this.clientService.getClient(_request);
|
||||||
|
@ -304,7 +212,7 @@ export class MastodonApiServerService {
|
||||||
reply.send(response);
|
reply.send(response);
|
||||||
});
|
});
|
||||||
|
|
||||||
fastify.post<{ Querystring: TimelineArgs, Params: { id?: string } }>('/v1/follow_requests/:id/reject', { preHandler: upload.single('none') }, async (_request, reply) => {
|
fastify.post<{ Params: { id?: string } }>('/v1/follow_requests/:id/reject', async (_request, reply) => {
|
||||||
if (!_request.params.id) return reply.code(400).send({ error: 'BAD_REQUEST', error_description: 'Missing required parameter "id"' });
|
if (!_request.params.id) return reply.code(400).send({ error: 'BAD_REQUEST', error_description: 'Missing required parameter "id"' });
|
||||||
|
|
||||||
const client = this.clientService.getClient(_request);
|
const client = this.clientService.getClient(_request);
|
||||||
|
@ -325,7 +233,7 @@ export class MastodonApiServerService {
|
||||||
focus?: string,
|
focus?: string,
|
||||||
is_sensitive?: string,
|
is_sensitive?: string,
|
||||||
},
|
},
|
||||||
}>('/v1/media/:id', { preHandler: upload.none() }, async (_request, reply) => {
|
}>('/v1/media/:id', async (_request, reply) => {
|
||||||
if (!_request.params.id) return reply.code(400).send({ error: 'BAD_REQUEST', error_description: 'Missing required parameter "id"' });
|
if (!_request.params.id) return reply.code(400).send({ error: 'BAD_REQUEST', error_description: 'Missing required parameter "id"' });
|
||||||
|
|
||||||
const options = {
|
const options = {
|
||||||
|
|
|
@ -11,7 +11,6 @@ import { DI } from '@/di-symbols.js';
|
||||||
import type { AccessTokensRepository, UserProfilesRepository } from '@/models/_.js';
|
import type { AccessTokensRepository, UserProfilesRepository } from '@/models/_.js';
|
||||||
import { attachMinMaxPagination } from '@/server/api/mastodon/pagination.js';
|
import { attachMinMaxPagination } from '@/server/api/mastodon/pagination.js';
|
||||||
import { MastodonConverters, convertRelationship, convertFeaturedTag, convertList } from '../MastodonConverters.js';
|
import { MastodonConverters, convertRelationship, convertFeaturedTag, convertList } from '../MastodonConverters.js';
|
||||||
import type multer from 'fastify-multer';
|
|
||||||
import type { FastifyInstance } from 'fastify';
|
import type { FastifyInstance } from 'fastify';
|
||||||
|
|
||||||
interface ApiAccountMastodonRoute {
|
interface ApiAccountMastodonRoute {
|
||||||
|
@ -34,7 +33,7 @@ export class ApiAccountMastodon {
|
||||||
private readonly driveService: DriveService,
|
private readonly driveService: DriveService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
public register(fastify: FastifyInstance, upload: ReturnType<typeof multer>): void {
|
public register(fastify: FastifyInstance): void {
|
||||||
fastify.get<ApiAccountMastodonRoute>('/v1/accounts/verify_credentials', async (_request, reply) => {
|
fastify.get<ApiAccountMastodonRoute>('/v1/accounts/verify_credentials', async (_request, reply) => {
|
||||||
const client = this.clientService.getClient(_request);
|
const client = this.clientService.getClient(_request);
|
||||||
const data = await client.verifyAccountCredentials();
|
const data = await client.verifyAccountCredentials();
|
||||||
|
@ -70,60 +69,50 @@ export class ApiAccountMastodon {
|
||||||
value: string,
|
value: string,
|
||||||
}[],
|
}[],
|
||||||
},
|
},
|
||||||
}>('/v1/accounts/update_credentials', { preHandler: upload.any() }, async (_request, reply) => {
|
}>('/v1/accounts/update_credentials', async (_request, reply) => {
|
||||||
const accessTokens = _request.headers.authorization;
|
const accessTokens = _request.headers.authorization;
|
||||||
const client = this.clientService.getClient(_request);
|
const client = this.clientService.getClient(_request);
|
||||||
// Check if there is a Header or Avatar being uploaded, if there is proceed to upload it to the drive of the user and then set it.
|
// Check if there is a Header or Avatar being uploaded, if there is proceed to upload it to the drive of the user and then set it.
|
||||||
if (_request.files.length > 0 && accessTokens) {
|
if (_request.savedRequestFiles?.length && accessTokens) {
|
||||||
const tokeninfo = await this.accessTokensRepository.findOneBy({ token: accessTokens.replace('Bearer ', '') });
|
const tokeninfo = await this.accessTokensRepository.findOneBy({ token: accessTokens.replace('Bearer ', '') });
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
const avatar = _request.savedRequestFiles.find(obj => {
|
||||||
const avatar = (_request.files as any).find((obj: any) => {
|
|
||||||
return obj.fieldname === 'avatar';
|
return obj.fieldname === 'avatar';
|
||||||
});
|
});
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
const header = _request.savedRequestFiles.find(obj => {
|
||||||
const header = (_request.files as any).find((obj: any) => {
|
|
||||||
return obj.fieldname === 'header';
|
return obj.fieldname === 'header';
|
||||||
});
|
});
|
||||||
|
|
||||||
if (tokeninfo && avatar) {
|
if (tokeninfo && avatar) {
|
||||||
const upload = await this.driveService.addFile({
|
const upload = await this.driveService.addFile({
|
||||||
user: { id: tokeninfo.userId, host: null },
|
user: { id: tokeninfo.userId, host: null },
|
||||||
path: avatar.path,
|
path: avatar.filepath,
|
||||||
name: avatar.originalname !== null && avatar.originalname !== 'file' ? avatar.originalname : undefined,
|
name: avatar.filename && avatar.filename !== 'file' ? avatar.filename : undefined,
|
||||||
sensitive: false,
|
sensitive: false,
|
||||||
});
|
});
|
||||||
if (upload.type.startsWith('image/')) {
|
if (upload.type.startsWith('image/')) {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
_request.body.avatar = upload.id;
|
||||||
(_request.body as any).avatar = upload.id;
|
|
||||||
}
|
}
|
||||||
} else if (tokeninfo && header) {
|
} else if (tokeninfo && header) {
|
||||||
const upload = await this.driveService.addFile({
|
const upload = await this.driveService.addFile({
|
||||||
user: { id: tokeninfo.userId, host: null },
|
user: { id: tokeninfo.userId, host: null },
|
||||||
path: header.path,
|
path: header.filepath,
|
||||||
name: header.originalname !== null && header.originalname !== 'file' ? header.originalname : undefined,
|
name: header.filename && header.filename !== 'file' ? header.filename : undefined,
|
||||||
sensitive: false,
|
sensitive: false,
|
||||||
});
|
});
|
||||||
if (upload.type.startsWith('image/')) {
|
if (upload.type.startsWith('image/')) {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
_request.body.header = upload.id;
|
||||||
(_request.body as any).header = upload.id;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
if (_request.body.fields_attributes) {
|
||||||
if ((_request.body as any).fields_attributes) {
|
for (const field of _request.body.fields_attributes) {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
const fields = (_request.body as any).fields_attributes.map((field: any) => {
|
|
||||||
if (!(field.name.trim() === '' && field.value.trim() === '')) {
|
if (!(field.name.trim() === '' && field.value.trim() === '')) {
|
||||||
if (field.name.trim() === '') return reply.code(400).send('Field name can not be empty');
|
if (field.name.trim() === '') return reply.code(400).send('Field name can not be empty');
|
||||||
if (field.value.trim() === '') return reply.code(400).send('Field value can not be empty');
|
if (field.value.trim() === '') return reply.code(400).send('Field value can not be empty');
|
||||||
}
|
}
|
||||||
return {
|
}
|
||||||
...field,
|
_request.body.fields_attributes = _request.body.fields_attributes.filter(field => field.name.trim().length > 0 && field.value.length > 0);
|
||||||
};
|
|
||||||
});
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
(_request.body as any).fields_attributes = fields.filter((field: any) => field.name.trim().length > 0 && field.value.length > 0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const options = {
|
const options = {
|
||||||
|
@ -234,7 +223,7 @@ export class ApiAccountMastodon {
|
||||||
reply.send(response);
|
reply.send(response);
|
||||||
});
|
});
|
||||||
|
|
||||||
fastify.post<ApiAccountMastodonRoute & { Params: { id?: string } }>('/v1/accounts/:id/follow', { preHandler: upload.single('none') }, async (_request, reply) => {
|
fastify.post<ApiAccountMastodonRoute & { Params: { id?: string } }>('/v1/accounts/:id/follow', async (_request, reply) => {
|
||||||
if (!_request.params.id) return reply.code(400).send({ error: 'BAD_REQUEST', error_description: 'Missing required parameter "id"' });
|
if (!_request.params.id) return reply.code(400).send({ error: 'BAD_REQUEST', error_description: 'Missing required parameter "id"' });
|
||||||
|
|
||||||
const client = this.clientService.getClient(_request);
|
const client = this.clientService.getClient(_request);
|
||||||
|
@ -245,7 +234,7 @@ export class ApiAccountMastodon {
|
||||||
reply.send(acct);
|
reply.send(acct);
|
||||||
});
|
});
|
||||||
|
|
||||||
fastify.post<ApiAccountMastodonRoute & { Params: { id?: string } }>('/v1/accounts/:id/unfollow', { preHandler: upload.single('none') }, async (_request, reply) => {
|
fastify.post<ApiAccountMastodonRoute & { Params: { id?: string } }>('/v1/accounts/:id/unfollow', async (_request, reply) => {
|
||||||
if (!_request.params.id) return reply.code(400).send({ error: 'BAD_REQUEST', error_description: 'Missing required parameter "id"' });
|
if (!_request.params.id) return reply.code(400).send({ error: 'BAD_REQUEST', error_description: 'Missing required parameter "id"' });
|
||||||
|
|
||||||
const client = this.clientService.getClient(_request);
|
const client = this.clientService.getClient(_request);
|
||||||
|
@ -256,7 +245,7 @@ export class ApiAccountMastodon {
|
||||||
reply.send(acct);
|
reply.send(acct);
|
||||||
});
|
});
|
||||||
|
|
||||||
fastify.post<ApiAccountMastodonRoute & { Params: { id?: string } }>('/v1/accounts/:id/block', { preHandler: upload.single('none') }, async (_request, reply) => {
|
fastify.post<ApiAccountMastodonRoute & { Params: { id?: string } }>('/v1/accounts/:id/block', async (_request, reply) => {
|
||||||
if (!_request.params.id) return reply.code(400).send({ error: 'BAD_REQUEST', error_description: 'Missing required parameter "id"' });
|
if (!_request.params.id) return reply.code(400).send({ error: 'BAD_REQUEST', error_description: 'Missing required parameter "id"' });
|
||||||
|
|
||||||
const client = this.clientService.getClient(_request);
|
const client = this.clientService.getClient(_request);
|
||||||
|
@ -266,7 +255,7 @@ export class ApiAccountMastodon {
|
||||||
reply.send(response);
|
reply.send(response);
|
||||||
});
|
});
|
||||||
|
|
||||||
fastify.post<ApiAccountMastodonRoute & { Params: { id?: string } }>('/v1/accounts/:id/unblock', { preHandler: upload.single('none') }, async (_request, reply) => {
|
fastify.post<ApiAccountMastodonRoute & { Params: { id?: string } }>('/v1/accounts/:id/unblock', async (_request, reply) => {
|
||||||
if (!_request.params.id) return reply.code(400).send({ error: 'BAD_REQUEST', error_description: 'Missing required parameter "id"' });
|
if (!_request.params.id) return reply.code(400).send({ error: 'BAD_REQUEST', error_description: 'Missing required parameter "id"' });
|
||||||
|
|
||||||
const client = this.clientService.getClient(_request);
|
const client = this.clientService.getClient(_request);
|
||||||
|
@ -276,7 +265,7 @@ export class ApiAccountMastodon {
|
||||||
return reply.send(response);
|
return reply.send(response);
|
||||||
});
|
});
|
||||||
|
|
||||||
fastify.post<ApiAccountMastodonRoute & { Params: { id?: string } }>('/v1/accounts/:id/mute', { preHandler: upload.single('none') }, async (_request, reply) => {
|
fastify.post<ApiAccountMastodonRoute & { Params: { id?: string } }>('/v1/accounts/:id/mute', async (_request, reply) => {
|
||||||
if (!_request.params.id) return reply.code(400).send({ error: 'BAD_REQUEST', error_description: 'Missing required parameter "id"' });
|
if (!_request.params.id) return reply.code(400).send({ error: 'BAD_REQUEST', error_description: 'Missing required parameter "id"' });
|
||||||
|
|
||||||
const client = this.clientService.getClient(_request);
|
const client = this.clientService.getClient(_request);
|
||||||
|
@ -289,7 +278,7 @@ export class ApiAccountMastodon {
|
||||||
reply.send(response);
|
reply.send(response);
|
||||||
});
|
});
|
||||||
|
|
||||||
fastify.post<ApiAccountMastodonRoute & { Params: { id?: string } }>('/v1/accounts/:id/unmute', { preHandler: upload.single('none') }, async (_request, reply) => {
|
fastify.post<ApiAccountMastodonRoute & { Params: { id?: string } }>('/v1/accounts/:id/unmute', async (_request, reply) => {
|
||||||
if (!_request.params.id) return reply.code(400).send({ error: 'BAD_REQUEST', error_description: 'Missing required parameter "id"' });
|
if (!_request.params.id) return reply.code(400).send({ error: 'BAD_REQUEST', error_description: 'Missing required parameter "id"' });
|
||||||
|
|
||||||
const client = this.clientService.getClient(_request);
|
const client = this.clientService.getClient(_request);
|
||||||
|
|
|
@ -6,7 +6,6 @@
|
||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { MastodonClientService } from '@/server/api/mastodon/MastodonClientService.js';
|
import { MastodonClientService } from '@/server/api/mastodon/MastodonClientService.js';
|
||||||
import type { FastifyInstance } from 'fastify';
|
import type { FastifyInstance } from 'fastify';
|
||||||
import type multer from 'fastify-multer';
|
|
||||||
|
|
||||||
const readScope = [
|
const readScope = [
|
||||||
'read:account',
|
'read:account',
|
||||||
|
@ -62,8 +61,8 @@ export class ApiAppsMastodon {
|
||||||
private readonly clientService: MastodonClientService,
|
private readonly clientService: MastodonClientService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
public register(fastify: FastifyInstance, upload: ReturnType<typeof multer>): void {
|
public register(fastify: FastifyInstance): void {
|
||||||
fastify.post<AuthMastodonRoute>('/v1/apps', { preHandler: upload.single('none') }, async (_request, reply) => {
|
fastify.post<AuthMastodonRoute>('/v1/apps', async (_request, reply) => {
|
||||||
const body = _request.body ?? _request.query;
|
const body = _request.body ?? _request.query;
|
||||||
if (!body.scopes) return reply.code(400).send({ error: 'BAD_REQUEST', error_description: 'Missing required payload "scopes"' });
|
if (!body.scopes) return reply.code(400).send({ error: 'BAD_REQUEST', error_description: 'Missing required payload "scopes"' });
|
||||||
if (!body.redirect_uris) return reply.code(400).send({ error: 'BAD_REQUEST', error_description: 'Missing required payload "redirect_uris"' });
|
if (!body.redirect_uris) return reply.code(400).send({ error: 'BAD_REQUEST', error_description: 'Missing required payload "redirect_uris"' });
|
||||||
|
|
|
@ -8,7 +8,6 @@ import { toBoolean } from '@/server/api/mastodon/argsUtils.js';
|
||||||
import { MastodonClientService } from '@/server/api/mastodon/MastodonClientService.js';
|
import { MastodonClientService } from '@/server/api/mastodon/MastodonClientService.js';
|
||||||
import { convertFilter } from '../MastodonConverters.js';
|
import { convertFilter } from '../MastodonConverters.js';
|
||||||
import type { FastifyInstance } from 'fastify';
|
import type { FastifyInstance } from 'fastify';
|
||||||
import type multer from 'fastify-multer';
|
|
||||||
|
|
||||||
interface ApiFilterMastodonRoute {
|
interface ApiFilterMastodonRoute {
|
||||||
Params: {
|
Params: {
|
||||||
|
@ -29,7 +28,7 @@ export class ApiFilterMastodon {
|
||||||
private readonly clientService: MastodonClientService,
|
private readonly clientService: MastodonClientService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
public register(fastify: FastifyInstance, upload: ReturnType<typeof multer>): void {
|
public register(fastify: FastifyInstance): void {
|
||||||
fastify.get('/v1/filters', async (_request, reply) => {
|
fastify.get('/v1/filters', async (_request, reply) => {
|
||||||
const client = this.clientService.getClient(_request);
|
const client = this.clientService.getClient(_request);
|
||||||
|
|
||||||
|
@ -49,7 +48,7 @@ export class ApiFilterMastodon {
|
||||||
reply.send(response);
|
reply.send(response);
|
||||||
});
|
});
|
||||||
|
|
||||||
fastify.post<ApiFilterMastodonRoute>('/v1/filters', { preHandler: upload.single('none') }, async (_request, reply) => {
|
fastify.post<ApiFilterMastodonRoute>('/v1/filters', async (_request, reply) => {
|
||||||
if (!_request.body.phrase) return reply.code(400).send({ error: 'BAD_REQUEST', error_description: 'Missing required payload "phrase"' });
|
if (!_request.body.phrase) return reply.code(400).send({ error: 'BAD_REQUEST', error_description: 'Missing required payload "phrase"' });
|
||||||
if (!_request.body.context) return reply.code(400).send({ error: 'BAD_REQUEST', error_description: 'Missing required payload "context"' });
|
if (!_request.body.context) return reply.code(400).send({ error: 'BAD_REQUEST', error_description: 'Missing required payload "context"' });
|
||||||
|
|
||||||
|
@ -68,7 +67,7 @@ export class ApiFilterMastodon {
|
||||||
reply.send(response);
|
reply.send(response);
|
||||||
});
|
});
|
||||||
|
|
||||||
fastify.post<ApiFilterMastodonRoute & { Params: { id?: string } }>('/v1/filters/:id', { preHandler: upload.single('none') }, async (_request, reply) => {
|
fastify.post<ApiFilterMastodonRoute & { Params: { id?: string } }>('/v1/filters/:id', async (_request, reply) => {
|
||||||
if (!_request.params.id) return reply.code(400).send({ error: 'BAD_REQUEST', error_description: 'Missing required parameter "id"' });
|
if (!_request.params.id) return reply.code(400).send({ error: 'BAD_REQUEST', error_description: 'Missing required parameter "id"' });
|
||||||
if (!_request.body.phrase) return reply.code(400).send({ error: 'BAD_REQUEST', error_description: 'Missing required payload "phrase"' });
|
if (!_request.body.phrase) return reply.code(400).send({ error: 'BAD_REQUEST', error_description: 'Missing required payload "phrase"' });
|
||||||
if (!_request.body.context) return reply.code(400).send({ error: 'BAD_REQUEST', error_description: 'Missing required payload "context"' });
|
if (!_request.body.context) return reply.code(400).send({ error: 'BAD_REQUEST', error_description: 'Missing required payload "context"' });
|
||||||
|
|
|
@ -10,7 +10,6 @@ import { MastodonConverters } from '@/server/api/mastodon/MastodonConverters.js'
|
||||||
import { attachMinMaxPagination } from '@/server/api/mastodon/pagination.js';
|
import { attachMinMaxPagination } from '@/server/api/mastodon/pagination.js';
|
||||||
import { MastodonClientService } from '../MastodonClientService.js';
|
import { MastodonClientService } from '../MastodonClientService.js';
|
||||||
import type { FastifyInstance } from 'fastify';
|
import type { FastifyInstance } from 'fastify';
|
||||||
import type multer from 'fastify-multer';
|
|
||||||
|
|
||||||
interface ApiNotifyMastodonRoute {
|
interface ApiNotifyMastodonRoute {
|
||||||
Params: {
|
Params: {
|
||||||
|
@ -26,7 +25,7 @@ export class ApiNotificationsMastodon {
|
||||||
private readonly clientService: MastodonClientService,
|
private readonly clientService: MastodonClientService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
public register(fastify: FastifyInstance, upload: ReturnType<typeof multer>): void {
|
public register(fastify: FastifyInstance): void {
|
||||||
fastify.get<ApiNotifyMastodonRoute>('/v1/notifications', async (request, reply) => {
|
fastify.get<ApiNotifyMastodonRoute>('/v1/notifications', async (request, reply) => {
|
||||||
const { client, me } = await this.clientService.getAuthClient(request);
|
const { client, me } = await this.clientService.getAuthClient(request);
|
||||||
const data = await client.getNotifications(parseTimelineArgs(request.query));
|
const data = await client.getNotifications(parseTimelineArgs(request.query));
|
||||||
|
@ -66,7 +65,7 @@ export class ApiNotificationsMastodon {
|
||||||
reply.send(response);
|
reply.send(response);
|
||||||
});
|
});
|
||||||
|
|
||||||
fastify.post<ApiNotifyMastodonRoute & { Params: { id?: string } }>('/v1/notification/:id/dismiss', { preHandler: upload.single('none') }, async (_request, reply) => {
|
fastify.post<ApiNotifyMastodonRoute & { Params: { id?: string } }>('/v1/notification/:id/dismiss', async (_request, reply) => {
|
||||||
if (!_request.params.id) return reply.code(400).send({ error: 'BAD_REQUEST', error_description: 'Missing required parameter "id"' });
|
if (!_request.params.id) return reply.code(400).send({ error: 'BAD_REQUEST', error_description: 'Missing required parameter "id"' });
|
||||||
|
|
||||||
const client = this.clientService.getClient(_request);
|
const client = this.clientService.getClient(_request);
|
||||||
|
@ -75,7 +74,7 @@ export class ApiNotificationsMastodon {
|
||||||
reply.send(data.data);
|
reply.send(data.data);
|
||||||
});
|
});
|
||||||
|
|
||||||
fastify.post<ApiNotifyMastodonRoute>('/v1/notifications/clear', { preHandler: upload.single('none') }, async (_request, reply) => {
|
fastify.post<ApiNotifyMastodonRoute>('/v1/notifications/clear', async (_request, reply) => {
|
||||||
const client = this.clientService.getClient(_request);
|
const client = this.clientService.getClient(_request);
|
||||||
const data = await client.dismissNotifications();
|
const data = await client.dismissNotifications();
|
||||||
|
|
||||||
|
|
|
@ -3,15 +3,14 @@
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import querystring from 'querystring';
|
|
||||||
import { Inject, Injectable } from '@nestjs/common';
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
import { v4 as uuid } from 'uuid';
|
import { v4 as uuid } from 'uuid';
|
||||||
import multer from 'fastify-multer';
|
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
import type { Config } from '@/config.js';
|
import type { Config } from '@/config.js';
|
||||||
import { DI } from '@/di-symbols.js';
|
import { DI } from '@/di-symbols.js';
|
||||||
import { MastodonClientService } from '@/server/api/mastodon/MastodonClientService.js';
|
import { MastodonClientService } from '@/server/api/mastodon/MastodonClientService.js';
|
||||||
import { getErrorData } from '@/server/api/mastodon/MastodonLogger.js';
|
import { getErrorData } from '@/server/api/mastodon/MastodonLogger.js';
|
||||||
|
import { ServerUtilityService } from '@/server/ServerUtilityService.js';
|
||||||
import type { FastifyInstance } from 'fastify';
|
import type { FastifyInstance } from 'fastify';
|
||||||
|
|
||||||
const kinds = [
|
const kinds = [
|
||||||
|
@ -56,6 +55,7 @@ export class OAuth2ProviderService {
|
||||||
private config: Config,
|
private config: Config,
|
||||||
|
|
||||||
private readonly mastodonClientService: MastodonClientService,
|
private readonly mastodonClientService: MastodonClientService,
|
||||||
|
private readonly serverUtilityService: ServerUtilityService,
|
||||||
) { }
|
) { }
|
||||||
|
|
||||||
// https://datatracker.ietf.org/doc/html/rfc8414.html
|
// https://datatracker.ietf.org/doc/html/rfc8414.html
|
||||||
|
@ -92,36 +92,10 @@ export class OAuth2ProviderService {
|
||||||
});
|
});
|
||||||
}); */
|
}); */
|
||||||
|
|
||||||
const upload = multer({
|
this.serverUtilityService.addMultipartFormDataContentType(fastify);
|
||||||
storage: multer.diskStorage({}),
|
this.serverUtilityService.addFormUrlEncodedContentType(fastify);
|
||||||
limits: {
|
this.serverUtilityService.addCORS(fastify);
|
||||||
fileSize: this.config.maxFileSize || 262144000,
|
this.serverUtilityService.addFlattenedQueryType(fastify);
|
||||||
files: 1,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
fastify.addHook('onRequest', (request, reply, done) => {
|
|
||||||
reply.header('Access-Control-Allow-Origin', '*');
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
|
|
||||||
fastify.addContentTypeParser('application/x-www-form-urlencoded', (request, payload, done) => {
|
|
||||||
let body = '';
|
|
||||||
payload.on('data', (data) => {
|
|
||||||
body += data;
|
|
||||||
});
|
|
||||||
payload.on('end', () => {
|
|
||||||
try {
|
|
||||||
const parsed = querystring.parse(body);
|
|
||||||
done(null, parsed);
|
|
||||||
} catch (e: unknown) {
|
|
||||||
done(e instanceof Error ? e : new Error(String(e)));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
payload.on('error', done);
|
|
||||||
});
|
|
||||||
|
|
||||||
fastify.register(multer.contentParser);
|
|
||||||
|
|
||||||
for (const url of ['/authorize', '/authorize/']) {
|
for (const url of ['/authorize', '/authorize/']) {
|
||||||
fastify.get<{ Querystring: Record<string, string | string[] | undefined> }>(url, async (request, reply) => {
|
fastify.get<{ Querystring: Record<string, string | string[] | undefined> }>(url, async (request, reply) => {
|
||||||
|
@ -136,7 +110,7 @@ export class OAuth2ProviderService {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fastify.post<{ Body?: Record<string, string | string[] | undefined>, Querystring: Record<string, string | string[] | undefined> }>('/token', { preHandler: upload.none() }, async (request, reply) => {
|
fastify.post<{ Body?: Record<string, string | string[] | undefined>, Querystring: Record<string, string | string[] | undefined> }>('/token', async (request, reply) => {
|
||||||
const body = request.body ?? request.query;
|
const body = request.body ?? request.query;
|
||||||
|
|
||||||
if (body.grant_type === 'client_credentials') {
|
if (body.grant_type === 'client_credentials') {
|
||||||
|
|
|
@ -1502,13 +1502,13 @@ export default class Misskey implements MegalodonInterface {
|
||||||
/**
|
/**
|
||||||
* POST /api/drive/files/create
|
* POST /api/drive/files/create
|
||||||
*/
|
*/
|
||||||
public async uploadMedia(file: any, _options?: { description?: string; focus?: string }): Promise<Response<Entity.Attachment>> {
|
public async uploadMedia(file: { filepath: fs.PathLike, mimetype: string, filename: string }, _options?: { description?: string; focus?: string }): Promise<Response<Entity.Attachment>> {
|
||||||
const formData = new FormData()
|
const formData = new FormData()
|
||||||
formData.append('file', fs.createReadStream(file.path), {
|
formData.append('file', fs.createReadStream(file.filepath), {
|
||||||
contentType: file.mimetype,
|
contentType: file.mimetype,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (file.originalname != null && file.originalname !== "file") formData.append("name", file.originalname);
|
if (file.filename && file.filename !== "file") formData.append("name", file.filename);
|
||||||
|
|
||||||
if (_options?.description != null) formData.append("comment", _options.description);
|
if (_options?.description != null) formData.append("comment", _options.description);
|
||||||
let headers: { [key: string]: string } = {}
|
let headers: { [key: string]: string } = {}
|
||||||
|
|
49
pnpm-lock.yaml
generated
49
pnpm-lock.yaml
generated
|
@ -233,9 +233,6 @@ importers:
|
||||||
fastify:
|
fastify:
|
||||||
specifier: 5.3.2
|
specifier: 5.3.2
|
||||||
version: 5.3.2
|
version: 5.3.2
|
||||||
fastify-multer:
|
|
||||||
specifier: ^2.0.3
|
|
||||||
version: 2.0.3
|
|
||||||
fastify-raw-body:
|
fastify-raw-body:
|
||||||
specifier: 5.0.0
|
specifier: 5.0.0
|
||||||
version: 5.0.0
|
version: 5.0.0
|
||||||
|
@ -2274,10 +2271,6 @@ packages:
|
||||||
'@fastify/ajv-compiler@4.0.1':
|
'@fastify/ajv-compiler@4.0.1':
|
||||||
resolution: {integrity: sha512-DxrBdgsjNLP0YM6W5Hd6/Fmj43S8zMKiFJYgi+Ri3htTGAowPVG/tG1wpnWLMjufEnehRivUCKZ1pLDIoZdTuw==}
|
resolution: {integrity: sha512-DxrBdgsjNLP0YM6W5Hd6/Fmj43S8zMKiFJYgi+Ri3htTGAowPVG/tG1wpnWLMjufEnehRivUCKZ1pLDIoZdTuw==}
|
||||||
|
|
||||||
'@fastify/busboy@1.2.1':
|
|
||||||
resolution: {integrity: sha512-7PQA7EH43S0CxcOa9OeAnaeA0oQ+e/DHNPZwSQM9CQHW76jle5+OvLdibRp/Aafs9KXbLhxyjOTkRjWUbQEd3Q==}
|
|
||||||
engines: {node: '>=14'}
|
|
||||||
|
|
||||||
'@fastify/busboy@2.1.0':
|
'@fastify/busboy@2.1.0':
|
||||||
resolution: {integrity: sha512-+KpH+QxZU7O4675t3mnkQKcZZg56u+K/Ct2K+N2AZYNVK8kyeo/bI18tI8aPm3tvNNRyTWfj6s5tnGNlcbQRsA==}
|
resolution: {integrity: sha512-+KpH+QxZU7O4675t3mnkQKcZZg56u+K/Ct2K+N2AZYNVK8kyeo/bI18tI8aPm3tvNNRyTWfj6s5tnGNlcbQRsA==}
|
||||||
engines: {node: '>=14'}
|
engines: {node: '>=14'}
|
||||||
|
@ -5455,10 +5448,6 @@ packages:
|
||||||
resolution: {integrity: sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==}
|
resolution: {integrity: sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==}
|
||||||
engines: {'0': node >= 0.8}
|
engines: {'0': node >= 0.8}
|
||||||
|
|
||||||
concat-stream@2.0.0:
|
|
||||||
resolution: {integrity: sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==}
|
|
||||||
engines: {'0': node >= 6.0}
|
|
||||||
|
|
||||||
config-chain@1.1.13:
|
config-chain@1.1.13:
|
||||||
resolution: {integrity: sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==}
|
resolution: {integrity: sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==}
|
||||||
|
|
||||||
|
@ -6313,13 +6302,6 @@ packages:
|
||||||
resolution: {integrity: sha512-xkjOecfnKGkSsOwtZ5Pz7Us/T6mrbPQrq0nh+aCO5V9nk5NLWmasAHumTKjiPJPWANe+kAZ84Jc8ooJkzZ88Sw==}
|
resolution: {integrity: sha512-xkjOecfnKGkSsOwtZ5Pz7Us/T6mrbPQrq0nh+aCO5V9nk5NLWmasAHumTKjiPJPWANe+kAZ84Jc8ooJkzZ88Sw==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
fastify-multer@2.0.3:
|
|
||||||
resolution: {integrity: sha512-QnFqrRgxmUwWHTgX9uyQSu0C/hmVCfcxopqjApZ4uaZD5W9MJ+nHUlW4+9q7Yd3BRxDIuHvgiM5mjrh6XG8cAA==}
|
|
||||||
engines: {node: '>=10.17.0'}
|
|
||||||
|
|
||||||
fastify-plugin@2.3.4:
|
|
||||||
resolution: {integrity: sha512-I+Oaj6p9oiRozbam30sh39BiuiqBda7yK2nmSPVwDCfIBlKnT8YB3MY+pRQc2Fcd07bf6KPGklHJaQ2Qu81TYQ==}
|
|
||||||
|
|
||||||
fastify-plugin@4.5.1:
|
fastify-plugin@4.5.1:
|
||||||
resolution: {integrity: sha512-stRHYGeuqpEZTL1Ef0Ovr2ltazUT9g844X5z/zEBFLG8RYlpDiOCIG+ATvYEp+/zmc7sN29mcIMp8gvYplYPIQ==}
|
resolution: {integrity: sha512-stRHYGeuqpEZTL1Ef0Ovr2ltazUT9g844X5z/zEBFLG8RYlpDiOCIG+ATvYEp+/zmc7sN29mcIMp8gvYplYPIQ==}
|
||||||
|
|
||||||
|
@ -9960,9 +9942,6 @@ packages:
|
||||||
resolution: {integrity: sha512-pFYqmTw68LXVjeWJMST4+borgQP2AyMNbg1BpZh9LbyhUeNkeaPF9gzfPGUAnSMV3qPYdWUwDIjjCLiSDOl7vg==}
|
resolution: {integrity: sha512-pFYqmTw68LXVjeWJMST4+borgQP2AyMNbg1BpZh9LbyhUeNkeaPF9gzfPGUAnSMV3qPYdWUwDIjjCLiSDOl7vg==}
|
||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
|
|
||||||
text-decoding@1.0.0:
|
|
||||||
resolution: {integrity: sha512-/0TJD42KDnVwKmDK6jj3xP7E2MG7SHAOG4tyTgyUCRPdHwvkquYNLEQltmdMa3owq3TkddCVcTsoctJI8VQNKA==}
|
|
||||||
|
|
||||||
textarea-caret@3.1.0:
|
textarea-caret@3.1.0:
|
||||||
resolution: {integrity: sha512-cXAvzO9pP5CGa6NKx0WYHl+8CHKZs8byMkt3PCJBCmq2a34YA9pO1NrQET5pzeqnBjBdToF5No4rrmkDUgQC2Q==}
|
resolution: {integrity: sha512-cXAvzO9pP5CGa6NKx0WYHl+8CHKZs8byMkt3PCJBCmq2a34YA9pO1NrQET5pzeqnBjBdToF5No4rrmkDUgQC2Q==}
|
||||||
|
|
||||||
|
@ -12060,10 +12039,6 @@ snapshots:
|
||||||
ajv-formats: 3.0.1(ajv@8.17.1)
|
ajv-formats: 3.0.1(ajv@8.17.1)
|
||||||
fast-uri: 3.0.1
|
fast-uri: 3.0.1
|
||||||
|
|
||||||
'@fastify/busboy@1.2.1':
|
|
||||||
dependencies:
|
|
||||||
text-decoding: 1.0.0
|
|
||||||
|
|
||||||
'@fastify/busboy@2.1.0': {}
|
'@fastify/busboy@2.1.0': {}
|
||||||
|
|
||||||
'@fastify/busboy@3.0.0': {}
|
'@fastify/busboy@3.0.0': {}
|
||||||
|
@ -15990,13 +15965,6 @@ snapshots:
|
||||||
readable-stream: 2.3.7
|
readable-stream: 2.3.7
|
||||||
typedarray: 0.0.6
|
typedarray: 0.0.6
|
||||||
|
|
||||||
concat-stream@2.0.0:
|
|
||||||
dependencies:
|
|
||||||
buffer-from: 1.1.2
|
|
||||||
inherits: 2.0.4
|
|
||||||
readable-stream: 3.6.0
|
|
||||||
typedarray: 0.0.6
|
|
||||||
|
|
||||||
config-chain@1.1.13:
|
config-chain@1.1.13:
|
||||||
dependencies:
|
dependencies:
|
||||||
ini: 1.3.8
|
ini: 1.3.8
|
||||||
|
@ -17122,21 +17090,6 @@ snapshots:
|
||||||
dependencies:
|
dependencies:
|
||||||
strnum: 1.0.5
|
strnum: 1.0.5
|
||||||
|
|
||||||
fastify-multer@2.0.3:
|
|
||||||
dependencies:
|
|
||||||
'@fastify/busboy': 1.2.1
|
|
||||||
append-field: 1.0.0
|
|
||||||
concat-stream: 2.0.0
|
|
||||||
fastify-plugin: 2.3.4
|
|
||||||
mkdirp: 1.0.4
|
|
||||||
on-finished: 2.4.1
|
|
||||||
type-is: 1.6.18
|
|
||||||
xtend: 4.0.2
|
|
||||||
|
|
||||||
fastify-plugin@2.3.4:
|
|
||||||
dependencies:
|
|
||||||
semver: 7.6.0
|
|
||||||
|
|
||||||
fastify-plugin@4.5.1: {}
|
fastify-plugin@4.5.1: {}
|
||||||
|
|
||||||
fastify-plugin@5.0.1: {}
|
fastify-plugin@5.0.1: {}
|
||||||
|
@ -21438,8 +21391,6 @@ snapshots:
|
||||||
glob: 10.4.5
|
glob: 10.4.5
|
||||||
minimatch: 9.0.5
|
minimatch: 9.0.5
|
||||||
|
|
||||||
text-decoding@1.0.0: {}
|
|
||||||
|
|
||||||
textarea-caret@3.1.0: {}
|
textarea-caret@3.1.0: {}
|
||||||
|
|
||||||
thenify-all@1.6.0:
|
thenify-all@1.6.0:
|
||||||
|
|
Loading…
Add table
Reference in a new issue