Merge tag '13.12.1' into merge-upstream
This commit is contained in:
@@ -11,7 +11,7 @@ import * as url from '@/misc/prelude/url.js';
|
||||
import type { Config } from '@/config.js';
|
||||
import { ApRendererService } from '@/core/activitypub/ApRendererService.js';
|
||||
import { QueueService } from '@/core/QueueService.js';
|
||||
import type { LocalUser, User } from '@/models/entities/User.js';
|
||||
import type { LocalUser, RemoteUser, User } from '@/models/entities/User.js';
|
||||
import { UserKeypairService } from '@/core/UserKeypairService.js';
|
||||
import type { Following } from '@/models/entities/Following.js';
|
||||
import { countIf } from '@/misc/prelude/array.js';
|
||||
@@ -630,7 +630,7 @@ export class ActivityPubServerService {
|
||||
id: request.params.followee,
|
||||
host: Not(IsNull()),
|
||||
}),
|
||||
]);
|
||||
]) as [LocalUser | RemoteUser | null, LocalUser | RemoteUser | null];
|
||||
|
||||
if (follower == null || followee == null) {
|
||||
reply.code(404);
|
||||
@@ -665,7 +665,7 @@ export class ActivityPubServerService {
|
||||
id: followRequest.followeeId,
|
||||
host: Not(IsNull()),
|
||||
}),
|
||||
]);
|
||||
]) as [LocalUser | RemoteUser | null, LocalUser | RemoteUser | null];
|
||||
|
||||
if (follower == null || followee == null) {
|
||||
reply.code(404);
|
||||
|
@@ -297,7 +297,8 @@ export class FileServerService {
|
||||
} else if ('badge' in request.query) {
|
||||
const mask = (await sharpBmp(file.path, file.mime))
|
||||
.resize(96, 96, {
|
||||
fit: 'inside',
|
||||
fit: 'contain',
|
||||
position: 'centre',
|
||||
withoutEnlargement: false,
|
||||
})
|
||||
.greyscale()
|
||||
@@ -453,7 +454,8 @@ export class FileServerService {
|
||||
fileRole: 'original',
|
||||
file,
|
||||
filename: file.name,
|
||||
mime: file.type,
|
||||
// 古いファイルは修正前のmimeを持っているのでできるだけ修正してあげる
|
||||
mime: this.fileInfoService.fixMime(file.type),
|
||||
ext: null,
|
||||
path,
|
||||
};
|
||||
|
@@ -8,6 +8,7 @@ import { escapeAttribute, escapeValue } from '@/misc/prelude/xml.js';
|
||||
import type { User } from '@/models/entities/User.js';
|
||||
import * as Acct from '@/misc/acct.js';
|
||||
import { NodeinfoServerService } from './NodeinfoServerService.js';
|
||||
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||
import type { FindOptionsWhere } from 'typeorm';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import type { FastifyInstance, FastifyPluginOptions } from 'fastify';
|
||||
@@ -23,6 +24,7 @@ export class WellKnownServerService {
|
||||
private usersRepository: UsersRepository,
|
||||
|
||||
private nodeinfoServerService: NodeinfoServerService,
|
||||
private userEntityService: UserEntityService,
|
||||
) {
|
||||
//this.createServer = this.createServer.bind(this);
|
||||
}
|
||||
@@ -130,7 +132,7 @@ fastify.get('/.well-known/change-password', async (request, reply) => {
|
||||
const self = {
|
||||
rel: 'self',
|
||||
type: 'application/activity+json',
|
||||
href: `${this.config.url}/users/${user.id}`,
|
||||
href: this.userEntityService.genLocalUserUri(user.id),
|
||||
};
|
||||
const profilePage = {
|
||||
rel: 'http://webfinger.net/rel/profile-page',
|
||||
|
@@ -261,6 +261,17 @@ export class ApiCallService implements OnApplicationShutdown {
|
||||
}
|
||||
}
|
||||
|
||||
if (ep.meta.prohibitMoved) {
|
||||
if (user?.movedToUri) {
|
||||
throw new ApiError({
|
||||
message: 'You have moved your account.',
|
||||
code: 'YOUR_ACCOUNT_MOVED',
|
||||
id: '56f20ec9-fd06-4fa5-841b-edd6d7d4fa31',
|
||||
httpStatusCode: 403,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if ((ep.meta.requireModerator || ep.meta.requireAdmin) && !user!.isRoot) {
|
||||
const myRoles = await this.roleService.getUserRoles(user!.id);
|
||||
if (ep.meta.requireModerator && !myRoles.some(r => r.isModerator || r.isAdministrator)) {
|
||||
|
@@ -29,6 +29,7 @@ import * as ep___admin_emoji_list from './endpoints/admin/emoji/list.js';
|
||||
import * as ep___admin_emoji_removeAliasesBulk from './endpoints/admin/emoji/remove-aliases-bulk.js';
|
||||
import * as ep___admin_emoji_setAliasesBulk from './endpoints/admin/emoji/set-aliases-bulk.js';
|
||||
import * as ep___admin_emoji_setCategoryBulk from './endpoints/admin/emoji/set-category-bulk.js';
|
||||
import * as ep___admin_emoji_setLicenseBulk from './endpoints/admin/emoji/set-license-bulk.js';
|
||||
import * as ep___admin_emoji_update from './endpoints/admin/emoji/update.js';
|
||||
import * as ep___admin_federation_deleteAllFiles from './endpoints/admin/federation/delete-all-files.js';
|
||||
import * as ep___admin_federation_refreshRemoteInstanceMetadata from './endpoints/admin/federation/refresh-remote-instance-metadata.js';
|
||||
@@ -193,6 +194,7 @@ import * as ep___i_exportMute from './endpoints/i/export-mute.js';
|
||||
import * as ep___i_exportNotes from './endpoints/i/export-notes.js';
|
||||
import * as ep___i_exportFavorites from './endpoints/i/export-favorites.js';
|
||||
import * as ep___i_exportUserLists from './endpoints/i/export-user-lists.js';
|
||||
import * as ep___i_exportAntennas from './endpoints/i/export-antennas.js';
|
||||
import * as ep___i_favorites from './endpoints/i/favorites.js';
|
||||
import * as ep___i_gallery_likes from './endpoints/i/gallery/likes.js';
|
||||
import * as ep___i_gallery_posts from './endpoints/i/gallery/posts.js';
|
||||
@@ -201,6 +203,7 @@ import * as ep___i_importBlocking from './endpoints/i/import-blocking.js';
|
||||
import * as ep___i_importFollowing from './endpoints/i/import-following.js';
|
||||
import * as ep___i_importMuting from './endpoints/i/import-muting.js';
|
||||
import * as ep___i_importUserLists from './endpoints/i/import-user-lists.js';
|
||||
import * as ep___i_importAntennas from './endpoints/i/import-antennas.js';
|
||||
import * as ep___i_notifications from './endpoints/i/notifications.js';
|
||||
import * as ep___i_pageLikes from './endpoints/i/page-likes.js';
|
||||
import * as ep___i_pages from './endpoints/i/pages.js';
|
||||
@@ -222,7 +225,6 @@ import * as ep___i_unpin from './endpoints/i/unpin.js';
|
||||
import * as ep___i_updateEmail from './endpoints/i/update-email.js';
|
||||
import * as ep___i_update from './endpoints/i/update.js';
|
||||
import * as ep___i_move from './endpoints/i/move.js';
|
||||
import * as ep___i_knownAs from './endpoints/i/known-as.js';
|
||||
import * as ep___i_webhooks_create from './endpoints/i/webhooks/create.js';
|
||||
import * as ep___i_webhooks_show from './endpoints/i/webhooks/show.js';
|
||||
import * as ep___i_webhooks_list from './endpoints/i/webhooks/list.js';
|
||||
@@ -330,6 +332,7 @@ import * as ep___users_search from './endpoints/users/search.js';
|
||||
import * as ep___users_show from './endpoints/users/show.js';
|
||||
import * as ep___users_stats from './endpoints/users/stats.js';
|
||||
import * as ep___users_achievements from './endpoints/users/achievements.js';
|
||||
import * as ep___users_updateMemo from './endpoints/users/update-memo.js';
|
||||
import * as ep___fetchRss from './endpoints/fetch-rss.js';
|
||||
import * as ep___retention from './endpoints/retention.js';
|
||||
import { GetterService } from './GetterService.js';
|
||||
@@ -364,6 +367,7 @@ const $admin_emoji_list: Provider = { provide: 'ep:admin/emoji/list', useClass:
|
||||
const $admin_emoji_removeAliasesBulk: Provider = { provide: 'ep:admin/emoji/remove-aliases-bulk', useClass: ep___admin_emoji_removeAliasesBulk.default };
|
||||
const $admin_emoji_setAliasesBulk: Provider = { provide: 'ep:admin/emoji/set-aliases-bulk', useClass: ep___admin_emoji_setAliasesBulk.default };
|
||||
const $admin_emoji_setCategoryBulk: Provider = { provide: 'ep:admin/emoji/set-category-bulk', useClass: ep___admin_emoji_setCategoryBulk.default };
|
||||
const $admin_emoji_setLicenseBulk: Provider = { provide: 'ep:admin/emoji/set-license-bulk', useClass: ep___admin_emoji_setLicenseBulk.default };
|
||||
const $admin_emoji_update: Provider = { provide: 'ep:admin/emoji/update', useClass: ep___admin_emoji_update.default };
|
||||
const $admin_federation_deleteAllFiles: Provider = { provide: 'ep:admin/federation/delete-all-files', useClass: ep___admin_federation_deleteAllFiles.default };
|
||||
const $admin_federation_refreshRemoteInstanceMetadata: Provider = { provide: 'ep:admin/federation/refresh-remote-instance-metadata', useClass: ep___admin_federation_refreshRemoteInstanceMetadata.default };
|
||||
@@ -528,6 +532,7 @@ const $i_exportMute: Provider = { provide: 'ep:i/export-mute', useClass: ep___i_
|
||||
const $i_exportNotes: Provider = { provide: 'ep:i/export-notes', useClass: ep___i_exportNotes.default };
|
||||
const $i_exportFavorites: Provider = { provide: 'ep:i/export-favorites', useClass: ep___i_exportFavorites.default };
|
||||
const $i_exportUserLists: Provider = { provide: 'ep:i/export-user-lists', useClass: ep___i_exportUserLists.default };
|
||||
const $i_exportAntennas: Provider = { provide: 'ep:i/export-antennas', useClass: ep___i_exportAntennas.default };
|
||||
const $i_favorites: Provider = { provide: 'ep:i/favorites', useClass: ep___i_favorites.default };
|
||||
const $i_gallery_likes: Provider = { provide: 'ep:i/gallery/likes', useClass: ep___i_gallery_likes.default };
|
||||
const $i_gallery_posts: Provider = { provide: 'ep:i/gallery/posts', useClass: ep___i_gallery_posts.default };
|
||||
@@ -536,6 +541,7 @@ const $i_importBlocking: Provider = { provide: 'ep:i/import-blocking', useClass:
|
||||
const $i_importFollowing: Provider = { provide: 'ep:i/import-following', useClass: ep___i_importFollowing.default };
|
||||
const $i_importMuting: Provider = { provide: 'ep:i/import-muting', useClass: ep___i_importMuting.default };
|
||||
const $i_importUserLists: Provider = { provide: 'ep:i/import-user-lists', useClass: ep___i_importUserLists.default };
|
||||
const $i_importAntennas: Provider = { provide: 'ep:i/import-antennas', useClass: ep___i_importAntennas.default };
|
||||
const $i_notifications: Provider = { provide: 'ep:i/notifications', useClass: ep___i_notifications.default };
|
||||
const $i_pageLikes: Provider = { provide: 'ep:i/page-likes', useClass: ep___i_pageLikes.default };
|
||||
const $i_pages: Provider = { provide: 'ep:i/pages', useClass: ep___i_pages.default };
|
||||
@@ -557,7 +563,6 @@ const $i_unpin: Provider = { provide: 'ep:i/unpin', useClass: ep___i_unpin.defau
|
||||
const $i_updateEmail: Provider = { provide: 'ep:i/update-email', useClass: ep___i_updateEmail.default };
|
||||
const $i_update: Provider = { provide: 'ep:i/update', useClass: ep___i_update.default };
|
||||
const $i_move: Provider = { provide: 'ep:i/move', useClass: ep___i_move.default };
|
||||
const $i_knownAs: Provider = { provide: 'ep:i/known-as', useClass: ep___i_knownAs.default };
|
||||
const $i_webhooks_create: Provider = { provide: 'ep:i/webhooks/create', useClass: ep___i_webhooks_create.default };
|
||||
const $i_webhooks_list: Provider = { provide: 'ep:i/webhooks/list', useClass: ep___i_webhooks_list.default };
|
||||
const $i_webhooks_show: Provider = { provide: 'ep:i/webhooks/show', useClass: ep___i_webhooks_show.default };
|
||||
@@ -665,6 +670,7 @@ const $users_search: Provider = { provide: 'ep:users/search', useClass: ep___use
|
||||
const $users_show: Provider = { provide: 'ep:users/show', useClass: ep___users_show.default };
|
||||
const $users_stats: Provider = { provide: 'ep:users/stats', useClass: ep___users_stats.default };
|
||||
const $users_achievements: Provider = { provide: 'ep:users/achievements', useClass: ep___users_achievements.default };
|
||||
const $users_updateMemo: Provider = { provide: 'ep:users/update-memo', useClass: ep___users_updateMemo.default };
|
||||
const $fetchRss: Provider = { provide: 'ep:fetch-rss', useClass: ep___fetchRss.default };
|
||||
const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention.default };
|
||||
|
||||
@@ -703,6 +709,7 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention
|
||||
$admin_emoji_removeAliasesBulk,
|
||||
$admin_emoji_setAliasesBulk,
|
||||
$admin_emoji_setCategoryBulk,
|
||||
$admin_emoji_setLicenseBulk,
|
||||
$admin_emoji_update,
|
||||
$admin_federation_deleteAllFiles,
|
||||
$admin_federation_refreshRemoteInstanceMetadata,
|
||||
@@ -867,6 +874,7 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention
|
||||
$i_exportNotes,
|
||||
$i_exportFavorites,
|
||||
$i_exportUserLists,
|
||||
$i_exportAntennas,
|
||||
$i_favorites,
|
||||
$i_gallery_likes,
|
||||
$i_gallery_posts,
|
||||
@@ -875,6 +883,7 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention
|
||||
$i_importFollowing,
|
||||
$i_importMuting,
|
||||
$i_importUserLists,
|
||||
$i_importAntennas,
|
||||
$i_notifications,
|
||||
$i_pageLikes,
|
||||
$i_pages,
|
||||
@@ -896,7 +905,6 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention
|
||||
$i_updateEmail,
|
||||
$i_update,
|
||||
$i_move,
|
||||
$i_knownAs,
|
||||
$i_webhooks_create,
|
||||
$i_webhooks_list,
|
||||
$i_webhooks_show,
|
||||
@@ -1004,6 +1012,7 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention
|
||||
$users_show,
|
||||
$users_stats,
|
||||
$users_achievements,
|
||||
$users_updateMemo,
|
||||
$fetchRss,
|
||||
$retention,
|
||||
],
|
||||
@@ -1036,6 +1045,7 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention
|
||||
$admin_emoji_removeAliasesBulk,
|
||||
$admin_emoji_setAliasesBulk,
|
||||
$admin_emoji_setCategoryBulk,
|
||||
$admin_emoji_setLicenseBulk,
|
||||
$admin_emoji_update,
|
||||
$admin_federation_deleteAllFiles,
|
||||
$admin_federation_refreshRemoteInstanceMetadata,
|
||||
@@ -1200,6 +1210,7 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention
|
||||
$i_exportNotes,
|
||||
$i_exportFavorites,
|
||||
$i_exportUserLists,
|
||||
$i_exportAntennas,
|
||||
$i_favorites,
|
||||
$i_gallery_likes,
|
||||
$i_gallery_posts,
|
||||
@@ -1208,6 +1219,7 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention
|
||||
$i_importFollowing,
|
||||
$i_importMuting,
|
||||
$i_importUserLists,
|
||||
$i_importAntennas,
|
||||
$i_notifications,
|
||||
$i_pageLikes,
|
||||
$i_pages,
|
||||
@@ -1229,7 +1241,6 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention
|
||||
$i_updateEmail,
|
||||
$i_update,
|
||||
$i_move,
|
||||
$i_knownAs,
|
||||
$i_webhooks_create,
|
||||
$i_webhooks_list,
|
||||
$i_webhooks_show,
|
||||
@@ -1335,6 +1346,7 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention
|
||||
$users_show,
|
||||
$users_stats,
|
||||
$users_achievements,
|
||||
$users_updateMemo,
|
||||
$fetchRss,
|
||||
$retention,
|
||||
],
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import Limiter from 'ratelimiter';
|
||||
import Redis from 'ioredis';
|
||||
import * as Redis from 'ioredis';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import type Logger from '@/logger.js';
|
||||
import { LoggerService } from '@/core/LoggerService.js';
|
||||
|
@@ -1,6 +1,7 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import rndstr from 'rndstr';
|
||||
import bcrypt from 'bcryptjs';
|
||||
import { IsNull } from 'typeorm';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import type { RegistrationTicketsRepository, UsedUsernamesRepository, UserPendingsRepository, UserProfilesRepository, UsersRepository } from '@/models/index.js';
|
||||
import type { Config } from '@/config.js';
|
||||
@@ -15,7 +16,6 @@ import { FastifyReplyError } from '@/misc/fastify-reply-error.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { SigninService } from './SigninService.js';
|
||||
import type { FastifyRequest, FastifyReply } from 'fastify';
|
||||
import { IsNull } from 'typeorm';
|
||||
|
||||
@Injectable()
|
||||
export class SignupApiService {
|
||||
@@ -137,6 +137,11 @@ export class SignupApiService {
|
||||
throw new FastifyReplyError(400, 'USED_USERNAME');
|
||||
}
|
||||
|
||||
const isPreserved = instance.preservedUsernames.map(x => x.toLowerCase()).includes(username.toLowerCase());
|
||||
if (isPreserved) {
|
||||
throw new FastifyReplyError(400, 'DENIED_USERNAME');
|
||||
}
|
||||
|
||||
const code = rndstr('a-z0-9', 16);
|
||||
|
||||
// Generate hash of password
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import { EventEmitter } from 'events';
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import Redis from 'ioredis';
|
||||
import * as Redis from 'ioredis';
|
||||
import * as websocket from 'websocket';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import type { UsersRepository, BlockingsRepository, ChannelFollowingsRepository, FollowingsRepository, MutingsRepository, UserProfilesRepository, RenoteMutingsRepository } from '@/models/index.js';
|
||||
|
@@ -29,6 +29,7 @@ import * as ep___admin_emoji_list from './endpoints/admin/emoji/list.js';
|
||||
import * as ep___admin_emoji_removeAliasesBulk from './endpoints/admin/emoji/remove-aliases-bulk.js';
|
||||
import * as ep___admin_emoji_setAliasesBulk from './endpoints/admin/emoji/set-aliases-bulk.js';
|
||||
import * as ep___admin_emoji_setCategoryBulk from './endpoints/admin/emoji/set-category-bulk.js';
|
||||
import * as ep___admin_emoji_setLicenseBulk from './endpoints/admin/emoji/set-license-bulk.js';
|
||||
import * as ep___admin_emoji_update from './endpoints/admin/emoji/update.js';
|
||||
import * as ep___admin_federation_deleteAllFiles from './endpoints/admin/federation/delete-all-files.js';
|
||||
import * as ep___admin_federation_refreshRemoteInstanceMetadata from './endpoints/admin/federation/refresh-remote-instance-metadata.js';
|
||||
@@ -193,6 +194,7 @@ import * as ep___i_exportMute from './endpoints/i/export-mute.js';
|
||||
import * as ep___i_exportNotes from './endpoints/i/export-notes.js';
|
||||
import * as ep___i_exportFavorites from './endpoints/i/export-favorites.js';
|
||||
import * as ep___i_exportUserLists from './endpoints/i/export-user-lists.js';
|
||||
import * as ep___i_exportAntennas from './endpoints/i/export-antennas.js';
|
||||
import * as ep___i_favorites from './endpoints/i/favorites.js';
|
||||
import * as ep___i_gallery_likes from './endpoints/i/gallery/likes.js';
|
||||
import * as ep___i_gallery_posts from './endpoints/i/gallery/posts.js';
|
||||
@@ -201,6 +203,7 @@ import * as ep___i_importBlocking from './endpoints/i/import-blocking.js';
|
||||
import * as ep___i_importFollowing from './endpoints/i/import-following.js';
|
||||
import * as ep___i_importMuting from './endpoints/i/import-muting.js';
|
||||
import * as ep___i_importUserLists from './endpoints/i/import-user-lists.js';
|
||||
import * as ep___i_importAntennas from './endpoints/i/import-antennas.js';
|
||||
import * as ep___i_notifications from './endpoints/i/notifications.js';
|
||||
import * as ep___i_pageLikes from './endpoints/i/page-likes.js';
|
||||
import * as ep___i_pages from './endpoints/i/pages.js';
|
||||
@@ -222,7 +225,6 @@ import * as ep___i_unpin from './endpoints/i/unpin.js';
|
||||
import * as ep___i_updateEmail from './endpoints/i/update-email.js';
|
||||
import * as ep___i_update from './endpoints/i/update.js';
|
||||
import * as ep___i_move from './endpoints/i/move.js';
|
||||
import * as ep___i_knownAs from './endpoints/i/known-as.js';
|
||||
import * as ep___i_webhooks_create from './endpoints/i/webhooks/create.js';
|
||||
import * as ep___i_webhooks_show from './endpoints/i/webhooks/show.js';
|
||||
import * as ep___i_webhooks_list from './endpoints/i/webhooks/list.js';
|
||||
@@ -330,6 +332,7 @@ import * as ep___users_search from './endpoints/users/search.js';
|
||||
import * as ep___users_show from './endpoints/users/show.js';
|
||||
import * as ep___users_stats from './endpoints/users/stats.js';
|
||||
import * as ep___users_achievements from './endpoints/users/achievements.js';
|
||||
import * as ep___users_updateMemo from './endpoints/users/update-memo.js';
|
||||
import * as ep___fetchRss from './endpoints/fetch-rss.js';
|
||||
import * as ep___retention from './endpoints/retention.js';
|
||||
|
||||
@@ -362,6 +365,7 @@ const eps = [
|
||||
['admin/emoji/remove-aliases-bulk', ep___admin_emoji_removeAliasesBulk],
|
||||
['admin/emoji/set-aliases-bulk', ep___admin_emoji_setAliasesBulk],
|
||||
['admin/emoji/set-category-bulk', ep___admin_emoji_setCategoryBulk],
|
||||
['admin/emoji/set-license-bulk', ep___admin_emoji_setLicenseBulk],
|
||||
['admin/emoji/update', ep___admin_emoji_update],
|
||||
['admin/federation/delete-all-files', ep___admin_federation_deleteAllFiles],
|
||||
['admin/federation/refresh-remote-instance-metadata', ep___admin_federation_refreshRemoteInstanceMetadata],
|
||||
@@ -526,6 +530,7 @@ const eps = [
|
||||
['i/export-notes', ep___i_exportNotes],
|
||||
['i/export-favorites', ep___i_exportFavorites],
|
||||
['i/export-user-lists', ep___i_exportUserLists],
|
||||
['i/export-antennas', ep___i_exportAntennas],
|
||||
['i/favorites', ep___i_favorites],
|
||||
['i/gallery/likes', ep___i_gallery_likes],
|
||||
['i/gallery/posts', ep___i_gallery_posts],
|
||||
@@ -534,6 +539,7 @@ const eps = [
|
||||
['i/import-following', ep___i_importFollowing],
|
||||
['i/import-muting', ep___i_importMuting],
|
||||
['i/import-user-lists', ep___i_importUserLists],
|
||||
['i/import-antennas', ep___i_importAntennas],
|
||||
['i/notifications', ep___i_notifications],
|
||||
['i/page-likes', ep___i_pageLikes],
|
||||
['i/pages', ep___i_pages],
|
||||
@@ -554,8 +560,7 @@ const eps = [
|
||||
['i/unpin', ep___i_unpin],
|
||||
['i/update-email', ep___i_updateEmail],
|
||||
['i/update', ep___i_update],
|
||||
//['i/move', ep___i_move],
|
||||
//['i/known-as', ep___i_knownAs],
|
||||
['i/move', ep___i_move],
|
||||
['i/webhooks/create', ep___i_webhooks_create],
|
||||
['i/webhooks/list', ep___i_webhooks_list],
|
||||
['i/webhooks/show', ep___i_webhooks_show],
|
||||
@@ -663,6 +668,7 @@ const eps = [
|
||||
['users/show', ep___users_show],
|
||||
['users/stats', ep___users_stats],
|
||||
['users/achievements', ep___users_achievements],
|
||||
['users/update-memo', ep___users_updateMemo],
|
||||
['fetch-rss', ep___fetchRss],
|
||||
['retention', ep___retention],
|
||||
];
|
||||
@@ -700,6 +706,12 @@ export interface IEndpointMeta {
|
||||
|
||||
readonly requireRolePolicy?: keyof RolePolicies;
|
||||
|
||||
/**
|
||||
* 引っ越し済みのユーザーによるリクエストを禁止するか
|
||||
* 省略した場合は false として解釈されます。
|
||||
*/
|
||||
readonly prohibitMoved?: boolean;
|
||||
|
||||
/**
|
||||
* エンドポイントのリミテーションに関するやつ
|
||||
* 省略した場合はリミテーションは無いものとして解釈されます。
|
||||
|
@@ -52,6 +52,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
const { account, secret } = await this.signupService.signup({
|
||||
username: ps.username,
|
||||
password: ps.password,
|
||||
ignorePreservedUsernames: true,
|
||||
});
|
||||
|
||||
const res = await this.userEntityService.pack(account, account, {
|
||||
|
@@ -87,12 +87,18 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
//const emojis = await q.take(ps.limit).getMany();
|
||||
|
||||
emojis = await q.getMany();
|
||||
const queryarry = ps.query.match(/\:([a-z0-9_]*)\:/g);
|
||||
|
||||
emojis = emojis.filter(emoji =>
|
||||
emoji.name.includes(ps.query!) ||
|
||||
emoji.aliases.some(a => a.includes(ps.query!)) ||
|
||||
emoji.category?.includes(ps.query!));
|
||||
|
||||
if (queryarry) {
|
||||
emojis = emojis.filter(emoji =>
|
||||
queryarry.includes(`:${emoji.name}:`)
|
||||
);
|
||||
} else {
|
||||
emojis = emojis.filter(emoji =>
|
||||
emoji.name.includes(ps.query!) ||
|
||||
emoji.aliases.some(a => a.includes(ps.query!)) ||
|
||||
emoji.category?.includes(ps.query!));
|
||||
}
|
||||
emojis.splice(ps.limit + 1);
|
||||
} else {
|
||||
emojis = await q.take(ps.limit).getMany();
|
||||
|
@@ -0,0 +1,37 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import { CustomEmojiService } from '@/core/CustomEmojiService.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['admin'],
|
||||
|
||||
requireCredential: true,
|
||||
requireRolePolicy: 'canManageCustomEmojis',
|
||||
} as const;
|
||||
|
||||
export const paramDef = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
ids: { type: 'array', items: {
|
||||
type: 'string', format: 'misskey:id',
|
||||
} },
|
||||
license: {
|
||||
type: 'string',
|
||||
nullable: true,
|
||||
description: 'Use `null` to reset the license.',
|
||||
},
|
||||
},
|
||||
required: ['ids'],
|
||||
} as const;
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
@Injectable()
|
||||
export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
constructor(
|
||||
private customEmojiService: CustomEmojiService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
await this.customEmojiService.setLicenseBulk(ps.ids, ps.license ?? null);
|
||||
});
|
||||
}
|
||||
}
|
@@ -39,9 +39,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
const pairs = await Promise.all(followings.map(f => Promise.all([
|
||||
this.usersRepository.findOneByOrFail({ id: f.followerId }),
|
||||
this.usersRepository.findOneByOrFail({ id: f.followeeId }),
|
||||
])));
|
||||
]).then(([from, to]) => [{ id: from.id }, { id: to.id }])));
|
||||
|
||||
this.queueService.createUnfollowJob(pairs.map(p => ({ to: p[0], from: p[1], silent: true })));
|
||||
this.queueService.createUnfollowJob(pairs.map(p => ({ from: p[0], to: p[1], silent: true })));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@@ -3,6 +3,7 @@ import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import type { InstancesRepository } from '@/models/index.js';
|
||||
import { UtilityService } from '@/core/UtilityService.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { FederatedInstanceService } from '@/core/FederatedInstanceService.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['admin'],
|
||||
@@ -28,6 +29,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
private instancesRepository: InstancesRepository,
|
||||
|
||||
private utilityService: UtilityService,
|
||||
private federatedInstanceService: FederatedInstanceService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const instance = await this.instancesRepository.findOneBy({ host: this.utilityService.toPuny(ps.host) });
|
||||
@@ -36,7 +38,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
throw new Error('instance not found');
|
||||
}
|
||||
|
||||
this.instancesRepository.update({ host: this.utilityService.toPuny(ps.host) }, {
|
||||
this.federatedInstanceService.update(instance.id, {
|
||||
isSuspended: ps.isSuspended,
|
||||
});
|
||||
});
|
||||
|
@@ -118,6 +118,14 @@ export const meta = {
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
},
|
||||
preservedUsernames: {
|
||||
type: 'array',
|
||||
optional: false, nullable: false,
|
||||
items: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
},
|
||||
hcaptchaSecretKey: {
|
||||
type: 'string',
|
||||
optional: true, nullable: true,
|
||||
@@ -311,6 +319,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
hiddenTags: instance.hiddenTags,
|
||||
blockedHosts: instance.blockedHosts,
|
||||
sensitiveWords: instance.sensitiveWords,
|
||||
preservedUsernames: instance.preservedUsernames,
|
||||
hcaptchaSecretKey: instance.hcaptchaSecretKey,
|
||||
recaptchaSecretKey: instance.recaptchaSecretKey,
|
||||
turnstileSecretKey: instance.turnstileSecretKey,
|
||||
|
@@ -25,6 +25,7 @@ export const paramDef = {
|
||||
isPublic: { type: 'boolean' },
|
||||
isModerator: { type: 'boolean' },
|
||||
isAdministrator: { type: 'boolean' },
|
||||
isExplorable: { type: 'boolean', default: false }, // optional for backward compatibility
|
||||
asBadge: { type: 'boolean' },
|
||||
canEditMembersByModerator: { type: 'boolean' },
|
||||
displayOrder: { type: 'number' },
|
||||
@@ -76,12 +77,13 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
isPublic: ps.isPublic,
|
||||
isAdministrator: ps.isAdministrator,
|
||||
isModerator: ps.isModerator,
|
||||
isExplorable: ps.isExplorable,
|
||||
asBadge: ps.asBadge,
|
||||
canEditMembersByModerator: ps.canEditMembersByModerator,
|
||||
displayOrder: ps.displayOrder,
|
||||
policies: ps.policies,
|
||||
}).then(x => this.rolesRepository.findOneByOrFail(x.identifiers[0]));
|
||||
|
||||
|
||||
this.globalEventService.publishInternalEvent('roleCreated', created);
|
||||
|
||||
return await this.roleEntityService.pack(created, me);
|
||||
|
@@ -33,6 +33,7 @@ export const paramDef = {
|
||||
isPublic: { type: 'boolean' },
|
||||
isModerator: { type: 'boolean' },
|
||||
isAdministrator: { type: 'boolean' },
|
||||
isExplorable: { type: 'boolean' },
|
||||
asBadge: { type: 'boolean' },
|
||||
canEditMembersByModerator: { type: 'boolean' },
|
||||
displayOrder: { type: 'number' },
|
||||
@@ -85,6 +86,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
isPublic: ps.isPublic,
|
||||
isModerator: ps.isModerator,
|
||||
isAdministrator: ps.isAdministrator,
|
||||
isExplorable: ps.isExplorable,
|
||||
asBadge: ps.asBadge,
|
||||
canEditMembersByModerator: ps.canEditMembersByModerator,
|
||||
displayOrder: ps.displayOrder,
|
||||
|
@@ -2,7 +2,7 @@ import * as os from 'node:os';
|
||||
import si from 'systeminformation';
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { DataSource } from 'typeorm';
|
||||
import Redis from 'ioredis';
|
||||
import * as Redis from 'ioredis';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
|
||||
|
@@ -80,7 +80,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
isSilenced: isSilenced,
|
||||
isSuspended: user.isSuspended,
|
||||
lastActiveDate: user.lastActiveDate,
|
||||
moderationNote: profile.moderationNote,
|
||||
moderationNote: profile.moderationNote ?? '',
|
||||
signins,
|
||||
policies: await this.roleService.getUserPolicies(user.id),
|
||||
roles: await this.roleEntityService.packMany(roles, me),
|
||||
|
@@ -94,6 +94,8 @@ export const paramDef = {
|
||||
enableActiveEmailValidation: { type: 'boolean' },
|
||||
enableChartsForRemoteUser: { type: 'boolean' },
|
||||
enableChartsForFederatedInstances: { type: 'boolean' },
|
||||
serverRules: { type: 'array', items: { type: 'string' } },
|
||||
preservedUsernames: { type: 'array', items: { type: 'string' } },
|
||||
},
|
||||
required: [],
|
||||
} as const;
|
||||
@@ -387,6 +389,14 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
set.enableChartsForFederatedInstances = ps.enableChartsForFederatedInstances;
|
||||
}
|
||||
|
||||
if (ps.serverRules !== undefined) {
|
||||
set.serverRules = ps.serverRules;
|
||||
}
|
||||
|
||||
if (ps.preservedUsernames !== undefined) {
|
||||
set.preservedUsernames = ps.preservedUsernames;
|
||||
}
|
||||
|
||||
await this.metaService.update(set);
|
||||
this.moderationLogService.insertModerationLog(me, 'updateMeta');
|
||||
});
|
||||
|
@@ -13,6 +13,8 @@ export const meta = {
|
||||
|
||||
requireCredential: true,
|
||||
|
||||
prohibitMoved: true,
|
||||
|
||||
kind: 'write:account',
|
||||
|
||||
errors: {
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import Redis from 'ioredis';
|
||||
import * as Redis from 'ioredis';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import type { NotesRepository, AntennasRepository } from '@/models/index.js';
|
||||
import { QueryService } from '@/core/QueryService.js';
|
||||
|
@@ -11,6 +11,8 @@ export const meta = {
|
||||
|
||||
requireCredential: true,
|
||||
|
||||
prohibitMoved: true,
|
||||
|
||||
kind: 'write:account',
|
||||
|
||||
errors: {
|
||||
@@ -71,7 +73,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
|
||||
@Inject(DI.userListsRepository)
|
||||
private userListsRepository: UserListsRepository,
|
||||
|
||||
|
||||
private antennaEntityService: AntennaEntityService,
|
||||
private globalEventService: GlobalEventService,
|
||||
) {
|
||||
|
@@ -13,6 +13,8 @@ export const meta = {
|
||||
|
||||
requireCredential: true,
|
||||
|
||||
prohibitMoved: true,
|
||||
|
||||
kind: 'write:channels',
|
||||
|
||||
limit: {
|
||||
@@ -41,6 +43,7 @@ export const paramDef = {
|
||||
name: { type: 'string', minLength: 1, maxLength: 128 },
|
||||
description: { type: 'string', nullable: true, minLength: 1, maxLength: 2048 },
|
||||
bannerId: { type: 'string', format: 'misskey:id', nullable: true },
|
||||
color: { type: 'string', minLength: 1, maxLength: 16 },
|
||||
},
|
||||
required: ['name'],
|
||||
} as const;
|
||||
@@ -78,6 +81,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
name: ps.name,
|
||||
description: ps.description ?? null,
|
||||
bannerId: banner ? banner.id : null,
|
||||
...(ps.color !== undefined ? { color: ps.color } : {}),
|
||||
} as Channel).then(x => this.channelsRepository.findOneByOrFail(x.identifiers[0]));
|
||||
|
||||
return await this.channelEntityService.pack(channel, me);
|
||||
|
@@ -10,6 +10,8 @@ export const meta = {
|
||||
|
||||
requireCredential: true,
|
||||
|
||||
prohibitMoved: true,
|
||||
|
||||
kind: 'write:channels',
|
||||
|
||||
errors: {
|
||||
|
@@ -38,6 +38,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const query = this.channelsRepository.createQueryBuilder('channel')
|
||||
.where('channel.lastNotedAt IS NOT NULL')
|
||||
.andWhere('channel.isArchived = FALSE')
|
||||
.orderBy('channel.lastNotedAt', 'DESC');
|
||||
|
||||
const channels = await query.take(10).getMany();
|
||||
|
@@ -11,6 +11,8 @@ export const meta = {
|
||||
|
||||
requireCredential: true,
|
||||
|
||||
prohibitMoved: true,
|
||||
|
||||
kind: 'write:channels',
|
||||
|
||||
errors: {
|
||||
|
@@ -44,7 +44,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
private queryService: QueryService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const query = this.queryService.makePaginationQuery(this.channelsRepository.createQueryBuilder(), ps.sinceId, ps.untilId)
|
||||
const query = this.queryService.makePaginationQuery(this.channelsRepository.createQueryBuilder('channel'), ps.sinceId, ps.untilId)
|
||||
.andWhere('channel.isArchived = FALSE')
|
||||
.andWhere({ userId: me.id });
|
||||
|
||||
const channels = await query
|
||||
|
@@ -46,15 +46,18 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
private queryService: QueryService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const query = this.queryService.makePaginationQuery(this.channelsRepository.createQueryBuilder('channel'), ps.sinceId, ps.untilId);
|
||||
const query = this.queryService.makePaginationQuery(this.channelsRepository.createQueryBuilder('channel'), ps.sinceId, ps.untilId)
|
||||
.andWhere('channel.isArchived = FALSE');
|
||||
|
||||
if (ps.type === 'nameAndDescription') {
|
||||
query.andWhere(new Brackets(qb => { qb
|
||||
.where('channel.name ILIKE :q', { q: `%${ sqlLikeEscape(ps.query) }%` })
|
||||
.orWhere('channel.description ILIKE :q', { q: `%${ sqlLikeEscape(ps.query) }%` });
|
||||
}));
|
||||
} else {
|
||||
query.andWhere('channel.name ILIKE :q', { q: `%${ sqlLikeEscape(ps.query) }%` });
|
||||
if (ps.query !== '') {
|
||||
if (ps.type === 'nameAndDescription') {
|
||||
query.andWhere(new Brackets(qb => { qb
|
||||
.where('channel.name ILIKE :q', { q: `%${ sqlLikeEscape(ps.query) }%` })
|
||||
.orWhere('channel.description ILIKE :q', { q: `%${ sqlLikeEscape(ps.query) }%` });
|
||||
}));
|
||||
} else {
|
||||
query.andWhere('channel.name ILIKE :q', { q: `%${ sqlLikeEscape(ps.query) }%` });
|
||||
}
|
||||
}
|
||||
|
||||
const channels = await query
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import Redis from 'ioredis';
|
||||
import * as Redis from 'ioredis';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import type { ChannelsRepository, Note, NotesRepository } from '@/models/index.js';
|
||||
import { QueryService } from '@/core/QueryService.js';
|
||||
|
@@ -9,6 +9,8 @@ export const meta = {
|
||||
|
||||
requireCredential: true,
|
||||
|
||||
prohibitMoved: true,
|
||||
|
||||
kind: 'write:channels',
|
||||
|
||||
errors: {
|
||||
|
@@ -10,6 +10,8 @@ export const meta = {
|
||||
|
||||
requireCredential: true,
|
||||
|
||||
prohibitMoved: true,
|
||||
|
||||
kind: 'write:channels',
|
||||
|
||||
errors: {
|
||||
|
@@ -47,12 +47,14 @@ export const paramDef = {
|
||||
name: { type: 'string', minLength: 1, maxLength: 128 },
|
||||
description: { type: 'string', nullable: true, minLength: 1, maxLength: 2048 },
|
||||
bannerId: { type: 'string', format: 'misskey:id', nullable: true },
|
||||
isArchived: { type: 'boolean', nullable: true },
|
||||
pinnedNoteIds: {
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'string', format: 'misskey:id',
|
||||
},
|
||||
},
|
||||
color: { type: 'string', minLength: 1, maxLength: 16 },
|
||||
},
|
||||
required: ['channelId'],
|
||||
} as const;
|
||||
@@ -104,6 +106,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
...(ps.name !== undefined ? { name: ps.name } : {}),
|
||||
...(ps.description !== undefined ? { description: ps.description } : {}),
|
||||
...(ps.pinnedNoteIds !== undefined ? { pinnedNoteIds: ps.pinnedNoteIds } : {}),
|
||||
...(ps.color !== undefined ? { color: ps.color } : {}),
|
||||
...(typeof ps.isArchived === 'boolean' ? { isArchived: ps.isArchived } : {}),
|
||||
...(banner ? { bannerId: banner.id } : {}),
|
||||
});
|
||||
|
||||
|
@@ -13,6 +13,8 @@ export const meta = {
|
||||
|
||||
requireCredential: true,
|
||||
|
||||
prohibitMoved: true,
|
||||
|
||||
kind: 'write:account',
|
||||
|
||||
limit: {
|
||||
|
@@ -12,6 +12,8 @@ export const meta = {
|
||||
|
||||
requireCredential: true,
|
||||
|
||||
prohibitMoved: true,
|
||||
|
||||
kind: 'write:account',
|
||||
|
||||
res: {
|
||||
@@ -57,7 +59,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
if (currentCount > (await this.roleService.getUserPolicies(me.id)).clipLimit) {
|
||||
throw new ApiError(meta.errors.tooManyClips);
|
||||
}
|
||||
|
||||
|
||||
const clip = await this.clipsRepository.insert({
|
||||
id: this.idService.genId(),
|
||||
createdAt: new Date(),
|
||||
|
@@ -10,6 +10,8 @@ export const meta = {
|
||||
|
||||
requireCredential: true,
|
||||
|
||||
prohibitMoved: true,
|
||||
|
||||
kind: 'write:clip-favorite',
|
||||
|
||||
errors: {
|
||||
|
@@ -10,6 +10,8 @@ export const meta = {
|
||||
|
||||
requireCredential: true,
|
||||
|
||||
prohibitMoved: true,
|
||||
|
||||
kind: 'write:account',
|
||||
|
||||
errors: {
|
||||
|
@@ -9,6 +9,8 @@ export const meta = {
|
||||
|
||||
requireCredential: true,
|
||||
|
||||
prohibitMoved: true,
|
||||
|
||||
kind: 'write:clip-favorite',
|
||||
|
||||
errors: {
|
||||
|
@@ -10,6 +10,8 @@ export const meta = {
|
||||
|
||||
requireCredential: true,
|
||||
|
||||
prohibitMoved: true,
|
||||
|
||||
kind: 'write:account',
|
||||
|
||||
errors: {
|
||||
|
@@ -15,6 +15,8 @@ export const meta = {
|
||||
|
||||
requireCredential: true,
|
||||
|
||||
prohibitMoved: true,
|
||||
|
||||
limit: {
|
||||
duration: ms('1hour'),
|
||||
max: 120,
|
||||
|
@@ -40,8 +40,13 @@ export const meta = {
|
||||
code: 'NO_SUCH_FOLDER',
|
||||
id: 'ea8fb7a5-af77-4a08-b608-c0218176cd73',
|
||||
},
|
||||
|
||||
restrictedByRole: {
|
||||
message: 'This feature is restricted by your role.',
|
||||
code: 'RESTRICTED_BY_ROLE',
|
||||
id: '7f59dccb-f465-75ab-5cf4-3ce44e3282f7',
|
||||
},
|
||||
},
|
||||
|
||||
res: {
|
||||
type: 'object',
|
||||
optional: false, nullable: false,
|
||||
@@ -77,7 +82,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const file = await this.driveFilesRepository.findOneBy({ id: ps.fileId });
|
||||
|
||||
const alwaysMarkNsfw = (await this.roleService.getUserPolicies(me.id)).alwaysMarkNsfw;
|
||||
if (file == null) {
|
||||
throw new ApiError(meta.errors.noSuchFile);
|
||||
}
|
||||
@@ -93,6 +98,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
|
||||
if (ps.comment !== undefined) file.comment = ps.comment;
|
||||
|
||||
if (ps.isSensitive !== undefined && ps.isSensitive !== file.isSensitive && alwaysMarkNsfw && !ps.isSensitive) {
|
||||
throw new ApiError(meta.errors.restrictedByRole);
|
||||
}
|
||||
|
||||
if (ps.isSensitive !== undefined) file.isSensitive = ps.isSensitive;
|
||||
|
||||
if (ps.folderId !== undefined) {
|
||||
|
@@ -19,6 +19,8 @@ export const meta = {
|
||||
|
||||
requireCredential: true,
|
||||
|
||||
prohibitMoved: true,
|
||||
|
||||
kind: 'write:drive',
|
||||
} as const;
|
||||
|
||||
|
@@ -11,6 +11,8 @@ export const meta = {
|
||||
|
||||
requireCredential: true,
|
||||
|
||||
prohibitMoved: true,
|
||||
|
||||
kind: 'write:flash',
|
||||
|
||||
limit: {
|
||||
|
@@ -10,6 +10,8 @@ export const meta = {
|
||||
|
||||
requireCredential: true,
|
||||
|
||||
prohibitMoved: true,
|
||||
|
||||
kind: 'write:flash-likes',
|
||||
|
||||
errors: {
|
||||
|
@@ -9,6 +9,8 @@ export const meta = {
|
||||
|
||||
requireCredential: true,
|
||||
|
||||
prohibitMoved: true,
|
||||
|
||||
kind: 'write:flash-likes',
|
||||
|
||||
errors: {
|
||||
|
@@ -10,6 +10,8 @@ export const meta = {
|
||||
|
||||
requireCredential: true,
|
||||
|
||||
prohibitMoved: true,
|
||||
|
||||
kind: 'write:flash',
|
||||
|
||||
limit: {
|
||||
|
@@ -19,6 +19,8 @@ export const meta = {
|
||||
|
||||
requireCredential: true,
|
||||
|
||||
prohibitMoved: true,
|
||||
|
||||
kind: 'write:following',
|
||||
|
||||
errors: {
|
||||
|
@@ -13,6 +13,8 @@ export const meta = {
|
||||
|
||||
requireCredential: true,
|
||||
|
||||
prohibitMoved: true,
|
||||
|
||||
kind: 'write:gallery',
|
||||
|
||||
limit: {
|
||||
|
@@ -10,6 +10,8 @@ export const meta = {
|
||||
|
||||
requireCredential: true,
|
||||
|
||||
prohibitMoved: true,
|
||||
|
||||
kind: 'write:gallery-likes',
|
||||
|
||||
errors: {
|
||||
|
@@ -9,6 +9,8 @@ export const meta = {
|
||||
|
||||
requireCredential: true,
|
||||
|
||||
prohibitMoved: true,
|
||||
|
||||
kind: 'write:gallery-likes',
|
||||
|
||||
errors: {
|
||||
|
@@ -11,6 +11,8 @@ export const meta = {
|
||||
|
||||
requireCredential: true,
|
||||
|
||||
prohibitMoved: true,
|
||||
|
||||
kind: 'write:gallery',
|
||||
|
||||
limit: {
|
||||
|
@@ -44,7 +44,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const query = this.usersRepository.createQueryBuilder('user')
|
||||
.where(':tag = ANY(user.tags)', { tag: normalizeForSearch(ps.tag) });
|
||||
.where(':tag = ANY(user.tags)', { tag: normalizeForSearch(ps.tag) })
|
||||
.andWhere('user.isSuspended = FALSE');
|
||||
|
||||
const recent = new Date(Date.now() - (1000 * 60 * 60 * 24 * 5));
|
||||
|
||||
|
@@ -4,6 +4,7 @@ import { AchievementService, ACHIEVEMENT_TYPES } from '@/core/AchievementService
|
||||
|
||||
export const meta = {
|
||||
requireCredential: true,
|
||||
prohibitMoved: true,
|
||||
} as const;
|
||||
|
||||
export const paramDef = {
|
||||
|
@@ -0,0 +1,30 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import ms from 'ms';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import { QueueService } from '@/core/QueueService.js';
|
||||
|
||||
export const meta = {
|
||||
secure: true,
|
||||
requireCredential: true,
|
||||
limit: {
|
||||
duration: ms('1hour'),
|
||||
max: 1,
|
||||
},
|
||||
} as const;
|
||||
|
||||
export const paramDef = {
|
||||
type: 'object',
|
||||
properties: {},
|
||||
required: [],
|
||||
} as const;
|
||||
|
||||
@Injectable()
|
||||
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
||||
constructor (
|
||||
private queueService: QueueService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
this.queueService.createExportAntennasJob(me);
|
||||
});
|
||||
}
|
||||
}
|
@@ -0,0 +1,84 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import ms from 'ms';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import { QueueService } from '@/core/QueueService.js';
|
||||
import type { AntennasRepository, DriveFilesRepository, UsersRepository, Antenna as _Antenna } from '@/models/index.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { RoleService } from '@/core/RoleService.js';
|
||||
import { DownloadService } from '@/core/DownloadService.js';
|
||||
import { ApiError } from '../../error.js';
|
||||
|
||||
export const meta = {
|
||||
secure: true,
|
||||
requireCredential: true,
|
||||
prohibitMoved: true,
|
||||
|
||||
limit: {
|
||||
duration: ms('1hour'),
|
||||
max: 1,
|
||||
},
|
||||
errors: {
|
||||
noSuchFile: {
|
||||
message: 'No such file.',
|
||||
code: 'NO_SUCH_FILE',
|
||||
id: '3b71d086-c3fa-431c-b01d-ded65a777172',
|
||||
},
|
||||
noSuchUser: {
|
||||
message: 'No such user.',
|
||||
code: 'NO_SUCH_USER',
|
||||
id: 'e842c379-8ac7-4cf7-b07a-4d4de7e4671c',
|
||||
},
|
||||
emptyFile: {
|
||||
message: 'That file is empty.',
|
||||
code: 'EMPTY_FILE',
|
||||
id: '7f60115d-8d93-4b0f-bd0e-3815dcbb389f',
|
||||
},
|
||||
tooManyAntennas: {
|
||||
message: 'You cannot create antenna any more.',
|
||||
code: 'TOO_MANY_ANTENNAS',
|
||||
id: '600917d4-a4cb-4cc5-8ba8-7ac8ea3c7779',
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
||||
export const paramDef = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
fileId: { type: 'string', format: 'misskey:id' },
|
||||
},
|
||||
required: ['fileId'],
|
||||
} as const;
|
||||
|
||||
@Injectable() // eslint-disable-next-line import/no-default-export
|
||||
export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
constructor (
|
||||
@Inject(DI.driveFilesRepository)
|
||||
private driveFilesRepository: DriveFilesRepository,
|
||||
|
||||
@Inject(DI.antennasRepository)
|
||||
private antennasRepository: AntennasRepository,
|
||||
|
||||
@Inject(DI.usersRepository)
|
||||
private usersRepository: UsersRepository,
|
||||
|
||||
private roleService: RoleService,
|
||||
private queueService: QueueService,
|
||||
private downloadService: DownloadService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const users = await this.usersRepository.findOneBy({ id: me.id });
|
||||
if (users === null) throw new ApiError(meta.errors.noSuchUser);
|
||||
const file = await this.driveFilesRepository.findOneBy({ id: ps.fileId });
|
||||
if (file === null) throw new ApiError(meta.errors.noSuchFile);
|
||||
if (file.size === 0) throw new ApiError(meta.errors.emptyFile);
|
||||
const antennas: (_Antenna & { userListAccts: string[] | null })[] = JSON.parse(await this.downloadService.downloadTextFile(file.url));
|
||||
const currentAntennasCount = await this.antennasRepository.countBy({ userId: me.id });
|
||||
if (currentAntennasCount + antennas.length > (await this.roleService.getUserPolicies(me.id)).antennaLimit) {
|
||||
throw new ApiError(meta.errors.tooManyAntennas);
|
||||
}
|
||||
this.queueService.createImportAntennasJob(me, antennas);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export type Antenna = (_Antenna & { userListAccts: string[] | null })[];
|
@@ -2,6 +2,7 @@ import { Inject, Injectable } from '@nestjs/common';
|
||||
import ms from 'ms';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import { QueueService } from '@/core/QueueService.js';
|
||||
import { AccountMoveService } from '@/core/AccountMoveService.js';
|
||||
import type { DriveFilesRepository } from '@/models/index.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { ApiError } from '../../error.js';
|
||||
@@ -9,6 +10,7 @@ import { ApiError } from '../../error.js';
|
||||
export const meta = {
|
||||
secure: true,
|
||||
requireCredential: true,
|
||||
prohibitMoved: true,
|
||||
|
||||
limit: {
|
||||
duration: ms('1hour'),
|
||||
@@ -58,15 +60,22 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
private driveFilesRepository: DriveFilesRepository,
|
||||
|
||||
private queueService: QueueService,
|
||||
private accountMoveService: AccountMoveService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const file = await this.driveFilesRepository.findOneBy({ id: ps.fileId });
|
||||
|
||||
if (file == null) throw new ApiError(meta.errors.noSuchFile);
|
||||
//if (!file.type.endsWith('/csv')) throw new ApiError(meta.errors.unexpectedFileType);
|
||||
if (file.size > 50000) throw new ApiError(meta.errors.tooBigFile);
|
||||
if (file.size === 0) throw new ApiError(meta.errors.emptyFile);
|
||||
|
||||
const checkMoving = await this.accountMoveService.validateAlsoKnownAs(
|
||||
me,
|
||||
(old, src) => !!src.movedAt && src.movedAt.getTime() + 1000 * 60 * 60 * 2 > (new Date()).getTime(),
|
||||
true
|
||||
);
|
||||
if (checkMoving ? file.size > 32 * 1024 * 1024 : file.size > 64 * 1024) throw new ApiError(meta.errors.tooBigFile);
|
||||
|
||||
this.queueService.createImportBlockingJob(me, file.id);
|
||||
});
|
||||
}
|
||||
|
@@ -2,6 +2,7 @@ import { Inject, Injectable } from '@nestjs/common';
|
||||
import ms from 'ms';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import { QueueService } from '@/core/QueueService.js';
|
||||
import { AccountMoveService } from '@/core/AccountMoveService.js';
|
||||
import type { DriveFilesRepository } from '@/models/index.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { ApiError } from '../../error.js';
|
||||
@@ -9,6 +10,7 @@ import { ApiError } from '../../error.js';
|
||||
export const meta = {
|
||||
secure: true,
|
||||
requireCredential: true,
|
||||
prohibitMoved: true,
|
||||
limit: {
|
||||
duration: ms('1hour'),
|
||||
max: 1,
|
||||
@@ -57,15 +59,22 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
private driveFilesRepository: DriveFilesRepository,
|
||||
|
||||
private queueService: QueueService,
|
||||
private accountMoveService: AccountMoveService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const file = await this.driveFilesRepository.findOneBy({ id: ps.fileId });
|
||||
|
||||
if (file == null) throw new ApiError(meta.errors.noSuchFile);
|
||||
//if (!file.type.endsWith('/csv')) throw new ApiError(meta.errors.unexpectedFileType);
|
||||
if (file.size > 50000) throw new ApiError(meta.errors.tooBigFile);
|
||||
if (file.size === 0) throw new ApiError(meta.errors.emptyFile);
|
||||
|
||||
const checkMoving = await this.accountMoveService.validateAlsoKnownAs(
|
||||
me,
|
||||
(old, src) => !!src.movedAt && src.movedAt.getTime() + 1000 * 60 * 60 * 2 > (new Date()).getTime(),
|
||||
true
|
||||
);
|
||||
if (checkMoving ? file.size > 32 * 1024 * 1024 : file.size > 64 * 1024) throw new ApiError(meta.errors.tooBigFile);
|
||||
|
||||
this.queueService.createImportFollowingJob(me, file.id);
|
||||
});
|
||||
}
|
||||
|
@@ -2,6 +2,7 @@ import { Inject, Injectable } from '@nestjs/common';
|
||||
import ms from 'ms';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import { QueueService } from '@/core/QueueService.js';
|
||||
import { AccountMoveService } from '@/core/AccountMoveService.js';
|
||||
import type { DriveFilesRepository } from '@/models/index.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { ApiError } from '../../error.js';
|
||||
@@ -9,6 +10,7 @@ import { ApiError } from '../../error.js';
|
||||
export const meta = {
|
||||
secure: true,
|
||||
requireCredential: true,
|
||||
prohibitMoved: true,
|
||||
|
||||
limit: {
|
||||
duration: ms('1hour'),
|
||||
@@ -58,15 +60,22 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
private driveFilesRepository: DriveFilesRepository,
|
||||
|
||||
private queueService: QueueService,
|
||||
private accountMoveService: AccountMoveService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const file = await this.driveFilesRepository.findOneBy({ id: ps.fileId });
|
||||
|
||||
if (file == null) throw new ApiError(meta.errors.noSuchFile);
|
||||
//if (!file.type.endsWith('/csv')) throw new ApiError(meta.errors.unexpectedFileType);
|
||||
if (file.size > 50000) throw new ApiError(meta.errors.tooBigFile);
|
||||
if (file.size === 0) throw new ApiError(meta.errors.emptyFile);
|
||||
|
||||
const checkMoving = await this.accountMoveService.validateAlsoKnownAs(
|
||||
me,
|
||||
(old, src) => !!src.movedAt && src.movedAt.getTime() + 1000 * 60 * 60 * 2 > (new Date()).getTime(),
|
||||
true
|
||||
);
|
||||
if (checkMoving ? file.size > 32 * 1024 * 1024 : file.size > 64 * 1024) throw new ApiError(meta.errors.tooBigFile);
|
||||
|
||||
this.queueService.createImportMutingJob(me, file.id);
|
||||
});
|
||||
}
|
||||
|
@@ -2,6 +2,7 @@ import { Inject, Injectable } from '@nestjs/common';
|
||||
import ms from 'ms';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import { QueueService } from '@/core/QueueService.js';
|
||||
import { AccountMoveService } from '@/core/AccountMoveService.js';
|
||||
import type { DriveFilesRepository } from '@/models/index.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { ApiError } from '../../error.js';
|
||||
@@ -9,6 +10,7 @@ import { ApiError } from '../../error.js';
|
||||
export const meta = {
|
||||
secure: true,
|
||||
requireCredential: true,
|
||||
prohibitMoved: true,
|
||||
limit: {
|
||||
duration: ms('1hour'),
|
||||
max: 1,
|
||||
@@ -57,15 +59,22 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
private driveFilesRepository: DriveFilesRepository,
|
||||
|
||||
private queueService: QueueService,
|
||||
private accountMoveService: AccountMoveService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const file = await this.driveFilesRepository.findOneBy({ id: ps.fileId });
|
||||
|
||||
if (file == null) throw new ApiError(meta.errors.noSuchFile);
|
||||
//if (!file.type.endsWith('/csv')) throw new ApiError(meta.errors.unexpectedFileType);
|
||||
if (file.size > 30000) throw new ApiError(meta.errors.tooBigFile);
|
||||
if (file.size === 0) throw new ApiError(meta.errors.emptyFile);
|
||||
|
||||
const checkMoving = await this.accountMoveService.validateAlsoKnownAs(
|
||||
me,
|
||||
(old, src) => !!src.movedAt && src.movedAt.getTime() + 1000 * 60 * 60 * 2 > (new Date()).getTime(),
|
||||
true
|
||||
);
|
||||
if (checkMoving ? file.size > 32 * 1024 * 1024 : file.size > 64 * 1024) throw new ApiError(meta.errors.tooBigFile);
|
||||
|
||||
this.queueService.createImportUserListsJob(me, file.id);
|
||||
});
|
||||
}
|
||||
|
@@ -1,92 +0,0 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import ms from 'ms';
|
||||
|
||||
import { User } from '@/models/entities/User.js';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import { ApiError } from '@/server/api/error.js';
|
||||
|
||||
import { AccountMoveService } from '@/core/AccountMoveService.js';
|
||||
import { RemoteUserResolveService } from '@/core/RemoteUserResolveService.js';
|
||||
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||
import { ApiLoggerService } from '@/server/api/ApiLoggerService.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['users'],
|
||||
|
||||
secure: true,
|
||||
requireCredential: true,
|
||||
|
||||
limit: {
|
||||
duration: ms('1day'),
|
||||
max: 30,
|
||||
},
|
||||
|
||||
errors: {
|
||||
noSuchUser: {
|
||||
message: 'No such user.',
|
||||
code: 'NO_SUCH_USER',
|
||||
id: 'fcd2eef9-a9b2-4c4f-8624-038099e90aa5',
|
||||
},
|
||||
notRemote: {
|
||||
message: 'User is not remote. You can only migrate from other instances.',
|
||||
code: 'NOT_REMOTE',
|
||||
id: '4362f8dc-731f-4ad8-a694-be2a88922a24',
|
||||
},
|
||||
uriNull: {
|
||||
message: 'User ActivityPup URI is null.',
|
||||
code: 'URI_NULL',
|
||||
id: 'bf326f31-d430-4f97-9933-5d61e4d48a23',
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
||||
export const paramDef = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
alsoKnownAs: { type: 'string' },
|
||||
},
|
||||
required: ['alsoKnownAs'],
|
||||
} as const;
|
||||
|
||||
@Injectable()
|
||||
export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
constructor(
|
||||
private userEntityService: UserEntityService,
|
||||
private remoteUserResolveService: RemoteUserResolveService,
|
||||
private apiLoggerService: ApiLoggerService,
|
||||
private accountMoveService: AccountMoveService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
// Check parameter
|
||||
if (!ps.alsoKnownAs) throw new ApiError(meta.errors.noSuchUser);
|
||||
|
||||
let unfiltered = ps.alsoKnownAs;
|
||||
const updates = {} as Partial<User>;
|
||||
|
||||
if (!unfiltered) {
|
||||
updates.alsoKnownAs = null;
|
||||
} else {
|
||||
// Parse user's input into the old account
|
||||
if (unfiltered.startsWith('acct:')) unfiltered = unfiltered.substring(5);
|
||||
if (unfiltered.startsWith('@')) unfiltered = unfiltered.substring(1);
|
||||
if (!unfiltered.includes('@')) throw new ApiError(meta.errors.notRemote);
|
||||
|
||||
const userAddress = unfiltered.split('@');
|
||||
// Retrieve the old account
|
||||
const knownAs = await this.remoteUserResolveService.resolveUser(userAddress[0], userAddress[1]).catch((e) => {
|
||||
this.apiLoggerService.logger.warn(`failed to resolve remote user: ${e}`);
|
||||
throw new ApiError(meta.errors.noSuchUser);
|
||||
});
|
||||
|
||||
const toUrl: string | null = knownAs.uri;
|
||||
if (!toUrl) throw new ApiError(meta.errors.uriNull);
|
||||
// Only allow moving from a remote account
|
||||
if (this.userEntityService.isLocalUser(knownAs)) throw new ApiError(meta.errors.notRemote);
|
||||
|
||||
updates.alsoKnownAs = updates.alsoKnownAs?.concat([toUrl]) ?? [toUrl];
|
||||
}
|
||||
|
||||
return await this.accountMoveService.createAlias(me, updates);
|
||||
});
|
||||
}
|
||||
}
|
@@ -7,40 +7,35 @@ import { DI } from '@/di-symbols.js';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import { ApiError } from '@/server/api/error.js';
|
||||
|
||||
import { LocalUser, RemoteUser } from '@/models/entities/User.js';
|
||||
|
||||
import { AccountMoveService } from '@/core/AccountMoveService.js';
|
||||
import { RemoteUserResolveService } from '@/core/RemoteUserResolveService.js';
|
||||
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||
import { ApiLoggerService } from '@/server/api/ApiLoggerService.js';
|
||||
import { GetterService } from '@/server/api/GetterService.js';
|
||||
import { ApPersonService } from '@/core/activitypub/models/ApPersonService.js';
|
||||
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||
|
||||
import * as Acct from '@/misc/acct.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['users'],
|
||||
|
||||
secure: true,
|
||||
requireCredential: true,
|
||||
prohibitMoved: true,
|
||||
limit: {
|
||||
duration: ms('1day'),
|
||||
max: 5,
|
||||
},
|
||||
|
||||
errors: {
|
||||
noSuchMoveTarget: {
|
||||
message: 'No such move target.',
|
||||
code: 'NO_SUCH_MOVE_TARGET',
|
||||
id: 'b5c90186-4ab0-49c8-9bba-a1f76c202ba4',
|
||||
},
|
||||
remoteAccountForbids: {
|
||||
destinationAccountForbids: {
|
||||
message:
|
||||
'Remote account doesn\'t have proper \'Known As\' alias. Did you remember to set it?',
|
||||
code: 'REMOTE_ACCOUNT_FORBIDS',
|
||||
'Destination account doesn\'t have proper \'Known As\' alias, or has already moved.',
|
||||
code: 'DESTINATION_ACCOUNT_FORBIDS',
|
||||
id: 'b5c90186-4ab0-49c8-9bba-a1f766282ba4',
|
||||
},
|
||||
notRemote: {
|
||||
message: 'User is not remote. You can only migrate to other instances.',
|
||||
code: 'NOT_REMOTE',
|
||||
id: '4362f8dc-731f-4ad8-a694-be2a88922a24',
|
||||
},
|
||||
rootForbidden: {
|
||||
message: 'The root can\'t migrate.',
|
||||
code: 'NOT_ROOT_FORBIDDEN',
|
||||
@@ -84,57 +79,52 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
@Inject(DI.config)
|
||||
private config: Config,
|
||||
|
||||
private userEntityService: UserEntityService,
|
||||
private remoteUserResolveService: RemoteUserResolveService,
|
||||
private apiLoggerService: ApiLoggerService,
|
||||
private accountMoveService: AccountMoveService,
|
||||
private getterService: GetterService,
|
||||
private apPersonService: ApPersonService,
|
||||
private userEntityService: UserEntityService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
// check parameter
|
||||
if (!ps.moveToAccount) throw new ApiError(meta.errors.noSuchMoveTarget);
|
||||
if (!ps.moveToAccount) throw new ApiError(meta.errors.noSuchUser);
|
||||
// abort if user is the root
|
||||
if (me.isRoot) throw new ApiError(meta.errors.rootForbidden);
|
||||
// abort if user has already moved
|
||||
if (me.movedToUri) throw new ApiError(meta.errors.alreadyMoved);
|
||||
|
||||
let unfiltered = ps.moveToAccount;
|
||||
if (!unfiltered) throw new ApiError(meta.errors.noSuchMoveTarget);
|
||||
|
||||
// parse user's input into the destination account
|
||||
if (unfiltered.startsWith('acct:')) unfiltered = unfiltered.substring(5);
|
||||
if (unfiltered.startsWith('@')) unfiltered = unfiltered.substring(1);
|
||||
if (!unfiltered.includes('@')) throw new ApiError(meta.errors.notRemote);
|
||||
|
||||
const userAddress = unfiltered.split('@');
|
||||
const { username, host } = Acct.parse(ps.moveToAccount);
|
||||
// retrieve the destination account
|
||||
let moveTo = await this.remoteUserResolveService.resolveUser(userAddress[0], userAddress[1]).catch((e) => {
|
||||
let moveTo = await this.remoteUserResolveService.resolveUser(username, host).catch((e) => {
|
||||
this.apiLoggerService.logger.warn(`failed to resolve remote user: ${e}`);
|
||||
throw new ApiError(meta.errors.noSuchMoveTarget);
|
||||
throw new ApiError(meta.errors.noSuchUser);
|
||||
});
|
||||
const remoteMoveTo = await this.getterService.getRemoteUser(moveTo.id);
|
||||
if (!remoteMoveTo.uri) throw new ApiError(meta.errors.uriNull);
|
||||
const destination = await this.getterService.getUser(moveTo.id) as LocalUser | RemoteUser;
|
||||
const newUri = this.userEntityService.getUserUri(destination);
|
||||
|
||||
// update local db
|
||||
await this.apPersonService.updatePerson(remoteMoveTo.uri);
|
||||
await this.apPersonService.updatePerson(newUri);
|
||||
// retrieve updated user
|
||||
moveTo = await this.apPersonService.resolvePerson(remoteMoveTo.uri);
|
||||
// only allow moving to a remote account
|
||||
if (this.userEntityService.isLocalUser(moveTo)) throw new ApiError(meta.errors.notRemote);
|
||||
moveTo = await this.apPersonService.resolvePerson(newUri);
|
||||
|
||||
let allowed = false;
|
||||
|
||||
const fromUrl = `${this.config.url}/users/${me.id}`;
|
||||
// make sure that the user has indicated the old account as an alias
|
||||
moveTo.alsoKnownAs?.forEach((elem) => {
|
||||
if (fromUrl.includes(elem)) allowed = true;
|
||||
});
|
||||
const fromUrl = this.userEntityService.genLocalUserUri(me.id);
|
||||
let allowed = false;
|
||||
if (moveTo.alsoKnownAs) {
|
||||
for (const knownAs of moveTo.alsoKnownAs) {
|
||||
if (knownAs.includes(fromUrl)) {
|
||||
allowed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// abort if unintended
|
||||
if (!(allowed && moveTo.uri && fromUrl)) throw new ApiError(meta.errors.remoteAccountForbids);
|
||||
if (!allowed || moveTo.movedToUri) throw new ApiError(meta.errors.destinationAccountForbids);
|
||||
|
||||
return await this.accountMoveService.moveToRemote(me, moveTo);
|
||||
return await this.accountMoveService.moveFromLocal(me, moveTo);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import { Brackets, In } from 'typeorm';
|
||||
import Redis from 'ioredis';
|
||||
import * as Redis from 'ioredis';
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import type { UsersRepository, FollowingsRepository, MutingsRepository, UserProfilesRepository, NotesRepository } from '@/models/index.js';
|
||||
import { obsoleteNotificationTypes, notificationTypes } from '@/types.js';
|
||||
|
@@ -8,6 +8,7 @@ export const meta = {
|
||||
tags: ['account', 'notes'],
|
||||
|
||||
requireCredential: true,
|
||||
prohibitMoved: true,
|
||||
|
||||
kind: 'write:account',
|
||||
|
||||
|
@@ -3,6 +3,7 @@ import * as mfm from 'mfm-js';
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { extractCustomEmojisFromMfm } from '@/misc/extract-custom-emojis-from-mfm.js';
|
||||
import { extractHashtags } from '@/misc/extract-hashtags.js';
|
||||
import * as Acct from '@/misc/acct.js';
|
||||
import type { UsersRepository, DriveFilesRepository, UserProfilesRepository, PagesRepository } from '@/models/index.js';
|
||||
import type { User } from '@/models/entities/User.js';
|
||||
import { birthdaySchema, descriptionSchema, locationSchema, nameSchema } from '@/models/entities/User.js';
|
||||
@@ -19,7 +20,10 @@ import { HashtagService } from '@/core/HashtagService.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { RoleService } from '@/core/RoleService.js';
|
||||
import { CacheService } from '@/core/CacheService.js';
|
||||
import { AccountMoveService } from '@/core/AccountMoveService.js';
|
||||
import { RemoteUserResolveService } from '@/core/RemoteUserResolveService.js';
|
||||
import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js';
|
||||
import { ApiLoggerService } from '../../ApiLoggerService.js';
|
||||
import { ApiError } from '../../error.js';
|
||||
|
||||
export const meta = {
|
||||
@@ -71,6 +75,30 @@ export const meta = {
|
||||
code: 'TOO_MANY_MUTED_WORDS',
|
||||
id: '010665b1-a211-42d2-bc64-8f6609d79785',
|
||||
},
|
||||
|
||||
noSuchUser: {
|
||||
message: 'No such user.',
|
||||
code: 'NO_SUCH_USER',
|
||||
id: 'fcd2eef9-a9b2-4c4f-8624-038099e90aa5',
|
||||
},
|
||||
|
||||
uriNull: {
|
||||
message: 'User ActivityPup URI is null.',
|
||||
code: 'URI_NULL',
|
||||
id: 'bf326f31-d430-4f97-9933-5d61e4d48a23',
|
||||
},
|
||||
|
||||
forbiddenToSetYourself: {
|
||||
message: 'You can\'t set yourself as your own alias.',
|
||||
code: 'FORBIDDEN_TO_SET_YOURSELF',
|
||||
id: '25c90186-4ab0-49c8-9bba-a1fa6c202ba4',
|
||||
},
|
||||
|
||||
restrictedByRole: {
|
||||
message: 'This feature is restricted by your role.',
|
||||
code: 'RESTRICTED_BY_ROLE',
|
||||
id: '8feff0ba-5ab5-585b-31f4-4df816663fad',
|
||||
}
|
||||
},
|
||||
|
||||
res: {
|
||||
@@ -129,6 +157,12 @@ export const paramDef = {
|
||||
emailNotificationTypes: { type: 'array', items: {
|
||||
type: 'string',
|
||||
} },
|
||||
alsoKnownAs: {
|
||||
type: 'array',
|
||||
maxItems: 10,
|
||||
uniqueItems: true,
|
||||
items: { type: 'string' },
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
||||
@@ -153,6 +187,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
private globalEventService: GlobalEventService,
|
||||
private userFollowingService: UserFollowingService,
|
||||
private accountUpdateService: AccountUpdateService,
|
||||
private accountMoveService: AccountMoveService,
|
||||
private remoteUserResolveService: RemoteUserResolveService,
|
||||
private apiLoggerService: ApiLoggerService,
|
||||
private hashtagService: HashtagService,
|
||||
private roleService: RoleService,
|
||||
private cacheService: CacheService,
|
||||
@@ -208,7 +245,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
if (typeof ps.isCat === 'boolean') updates.isCat = ps.isCat;
|
||||
if (typeof ps.injectFeaturedNote === 'boolean') profileUpdates.injectFeaturedNote = ps.injectFeaturedNote;
|
||||
if (typeof ps.receiveAnnouncementEmail === 'boolean') profileUpdates.receiveAnnouncementEmail = ps.receiveAnnouncementEmail;
|
||||
if (typeof ps.alwaysMarkNsfw === 'boolean') profileUpdates.alwaysMarkNsfw = ps.alwaysMarkNsfw;
|
||||
if (typeof ps.alwaysMarkNsfw === 'boolean') {
|
||||
if ((await roleService.getUserPolicies(user.id)).alwaysMarkNsfw) throw new ApiError(meta.errors.restrictedByRole);
|
||||
profileUpdates.alwaysMarkNsfw = ps.alwaysMarkNsfw;
|
||||
}
|
||||
if (typeof ps.autoSensitive === 'boolean') profileUpdates.autoSensitive = ps.autoSensitive;
|
||||
if (ps.emailNotificationTypes !== undefined) profileUpdates.emailNotificationTypes = ps.emailNotificationTypes;
|
||||
|
||||
@@ -221,6 +261,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
updates.avatarId = avatar.id;
|
||||
updates.avatarUrl = this.driveFileEntityService.getPublicUrl(avatar, 'avatar');
|
||||
updates.avatarBlurhash = avatar.blurhash;
|
||||
} else if (ps.avatarId === null) {
|
||||
updates.avatarId = null;
|
||||
updates.avatarUrl = null;
|
||||
updates.avatarBlurhash = null;
|
||||
}
|
||||
|
||||
if (ps.bannerId) {
|
||||
@@ -232,6 +276,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
updates.bannerId = banner.id;
|
||||
updates.bannerUrl = this.driveFileEntityService.getPublicUrl(banner);
|
||||
updates.bannerBlurhash = banner.blurhash;
|
||||
} else if (ps.bannerId === null) {
|
||||
updates.bannerId = null;
|
||||
updates.bannerUrl = null;
|
||||
updates.bannerBlurhash = null;
|
||||
}
|
||||
|
||||
if (ps.pinnedPageId) {
|
||||
@@ -252,6 +300,38 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
});
|
||||
}
|
||||
|
||||
if (ps.alsoKnownAs) {
|
||||
if (_user.movedToUri) {
|
||||
throw new ApiError({
|
||||
message: 'You have moved your account.',
|
||||
code: 'YOUR_ACCOUNT_MOVED',
|
||||
id: '56f20ec9-fd06-4fa5-841b-edd6d7d4fa31',
|
||||
httpStatusCode: 403,
|
||||
});
|
||||
}
|
||||
|
||||
// Parse user's input into the old account
|
||||
const newAlsoKnownAs = new Set<string>();
|
||||
for (const line of ps.alsoKnownAs) {
|
||||
if (!line) throw new ApiError(meta.errors.noSuchUser);
|
||||
const { username, host } = Acct.parse(line);
|
||||
|
||||
// Retrieve the old account
|
||||
const knownAs = await this.remoteUserResolveService.resolveUser(username, host).catch((e) => {
|
||||
this.apiLoggerService.logger.warn(`failed to resolve dstination user: ${e}`);
|
||||
throw new ApiError(meta.errors.noSuchUser);
|
||||
});
|
||||
if (knownAs.id === _user.id) throw new ApiError(meta.errors.forbiddenToSetYourself);
|
||||
|
||||
const toUrl = this.userEntityService.getUserUri(knownAs);
|
||||
if (!toUrl) throw new ApiError(meta.errors.uriNull);
|
||||
|
||||
newAlsoKnownAs.add(toUrl);
|
||||
}
|
||||
|
||||
updates.alsoKnownAs = newAlsoKnownAs.size > 0 ? Array.from(newAlsoKnownAs) : null;
|
||||
}
|
||||
|
||||
//#region emojis/tags
|
||||
|
||||
let emojis = [] as string[];
|
||||
@@ -279,6 +359,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
//#endregion
|
||||
|
||||
if (Object.keys(updates).length > 0) await this.usersRepository.update(user.id, updates);
|
||||
if (Object.keys(updates).includes('alsoKnownAs')) {
|
||||
this.cacheService.uriPersonCache.set(this.userEntityService.genLocalUserUri(user.id), { ...user, ...updates });
|
||||
}
|
||||
if (Object.keys(profileUpdates).length > 0) await this.userProfilesRepository.update(user.id, profileUpdates);
|
||||
|
||||
const iObj = await this.userEntityService.pack<true, true>(user.id, user, {
|
||||
|
@@ -201,10 +201,6 @@ export const meta = {
|
||||
type: 'boolean',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
elasticsearch: {
|
||||
type: 'boolean',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
hcaptcha: {
|
||||
type: 'boolean',
|
||||
optional: false, nullable: false,
|
||||
@@ -310,6 +306,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
|
||||
translatorAvailable: instance.deeplAuthKey != null,
|
||||
|
||||
serverRules: instance.serverRules,
|
||||
|
||||
policies: { ...DEFAULT_POLICIES, ...instance.policies },
|
||||
|
||||
mediaProxy: this.config.mediaProxy,
|
||||
@@ -329,7 +327,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
response.features = {
|
||||
registration: !instance.disableRegistration,
|
||||
emailRequiredForSignup: instance.emailRequiredForSignup,
|
||||
elasticsearch: this.config.elasticsearch ? true : false,
|
||||
hcaptcha: instance.enableHcaptcha,
|
||||
recaptcha: instance.enableRecaptcha,
|
||||
turnstile: instance.enableTurnstile,
|
||||
|
@@ -11,6 +11,7 @@ export const meta = {
|
||||
tags: ['account'],
|
||||
|
||||
requireCredential: true,
|
||||
prohibitMoved: true,
|
||||
|
||||
kind: 'write:mutes',
|
||||
|
||||
|
@@ -18,6 +18,8 @@ export const meta = {
|
||||
|
||||
requireCredential: true,
|
||||
|
||||
prohibitMoved: true,
|
||||
|
||||
limit: {
|
||||
duration: ms('1hour'),
|
||||
max: 300,
|
||||
@@ -260,7 +262,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
|
||||
let channel: Channel | null = null;
|
||||
if (ps.channelId != null) {
|
||||
channel = await this.channelsRepository.findOneBy({ id: ps.channelId });
|
||||
channel = await this.channelsRepository.findOneBy({ id: ps.channelId, isArchived: false });
|
||||
|
||||
if (channel == null) {
|
||||
throw new ApiError(meta.errors.noSuchChannel);
|
||||
|
@@ -12,6 +12,7 @@ export const meta = {
|
||||
tags: ['notes', 'favorites'],
|
||||
|
||||
requireCredential: true,
|
||||
prohibitMoved: true,
|
||||
|
||||
kind: 'write:favorites',
|
||||
|
||||
|
@@ -17,6 +17,8 @@ export const meta = {
|
||||
|
||||
requireCredential: true,
|
||||
|
||||
prohibitMoved: true,
|
||||
|
||||
kind: 'write:votes',
|
||||
|
||||
errors: {
|
||||
|
@@ -9,6 +9,8 @@ export const meta = {
|
||||
|
||||
requireCredential: true,
|
||||
|
||||
prohibitMoved: true,
|
||||
|
||||
kind: 'write:reactions',
|
||||
|
||||
errors: {
|
||||
|
@@ -1,11 +1,10 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import type { NotesRepository } from '@/models/index.js';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import { QueryService } from '@/core/QueryService.js';
|
||||
import { SearchService } from '@/core/SearchService.js';
|
||||
import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
|
||||
import type { Config } from '@/config.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { sqlLikeEscape } from '@/misc/sql-like-escape.js';
|
||||
import { RoleService } from '@/core/RoleService.js';
|
||||
import { ApiError } from '../../error.js';
|
||||
|
||||
@@ -43,8 +42,7 @@ export const paramDef = {
|
||||
offset: { type: 'integer', default: 0 },
|
||||
host: {
|
||||
type: 'string',
|
||||
nullable: true,
|
||||
description: 'The local host is represented with `null`.',
|
||||
description: 'The local host is represented with `.`.',
|
||||
},
|
||||
userId: { type: 'string', format: 'misskey:id', nullable: true, default: null },
|
||||
channelId: { type: 'string', format: 'misskey:id', nullable: true, default: null },
|
||||
@@ -61,11 +59,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
@Inject(DI.config)
|
||||
private config: Config,
|
||||
|
||||
@Inject(DI.notesRepository)
|
||||
private notesRepository: NotesRepository,
|
||||
|
||||
private noteEntityService: NoteEntityService,
|
||||
private queryService: QueryService,
|
||||
private searchService: SearchService,
|
||||
private roleService: RoleService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
@@ -74,27 +69,15 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
throw new ApiError(meta.errors.unavailable);
|
||||
}
|
||||
|
||||
const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'), ps.sinceId, ps.untilId);
|
||||
|
||||
if (ps.userId) {
|
||||
query.andWhere('note.userId = :userId', { userId: ps.userId });
|
||||
} else if (ps.channelId) {
|
||||
query.andWhere('note.channelId = :channelId', { channelId: ps.channelId });
|
||||
}
|
||||
|
||||
query
|
||||
.andWhere('note.text ILIKE :q', { q: `%${ sqlLikeEscape(ps.query) }%` })
|
||||
.innerJoinAndSelect('note.user', 'user')
|
||||
.leftJoinAndSelect('note.reply', 'reply')
|
||||
.leftJoinAndSelect('note.renote', 'renote')
|
||||
.leftJoinAndSelect('reply.user', 'replyUser')
|
||||
.leftJoinAndSelect('renote.user', 'renoteUser');
|
||||
|
||||
this.queryService.generateVisibilityQuery(query, me);
|
||||
if (me) this.queryService.generateMutedUserQuery(query, me);
|
||||
if (me) this.queryService.generateBlockedUserQuery(query, me);
|
||||
|
||||
const notes = await query.take(ps.limit).getMany();
|
||||
const notes = await this.searchService.searchNote(ps.query, me, {
|
||||
userId: ps.userId,
|
||||
channelId: ps.channelId,
|
||||
host: ps.host,
|
||||
}, {
|
||||
untilId: ps.untilId,
|
||||
sinceId: ps.sinceId,
|
||||
limit: ps.limit,
|
||||
});
|
||||
|
||||
return await this.noteEntityService.packMany(notes, me);
|
||||
});
|
||||
|
@@ -13,6 +13,8 @@ export const meta = {
|
||||
|
||||
requireCredential: true,
|
||||
|
||||
prohibitMoved: true,
|
||||
|
||||
kind: 'write:pages',
|
||||
|
||||
limit: {
|
||||
|
@@ -10,6 +10,8 @@ export const meta = {
|
||||
|
||||
requireCredential: true,
|
||||
|
||||
prohibitMoved: true,
|
||||
|
||||
kind: 'write:page-likes',
|
||||
|
||||
errors: {
|
||||
|
@@ -9,6 +9,8 @@ export const meta = {
|
||||
|
||||
requireCredential: true,
|
||||
|
||||
prohibitMoved: true,
|
||||
|
||||
kind: 'write:page-likes',
|
||||
|
||||
errors: {
|
||||
|
@@ -11,6 +11,8 @@ export const meta = {
|
||||
|
||||
requireCredential: true,
|
||||
|
||||
prohibitMoved: true,
|
||||
|
||||
kind: 'write:pages',
|
||||
|
||||
limit: {
|
||||
|
@@ -13,6 +13,7 @@ export const meta = {
|
||||
tags: ['account'],
|
||||
|
||||
requireCredential: true,
|
||||
prohibitMoved: true,
|
||||
|
||||
kind: 'write:mutes',
|
||||
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { DataSource } from 'typeorm';
|
||||
import Redis from 'ioredis';
|
||||
import * as Redis from 'ioredis';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { resetDb } from '@/misc/reset-db.js';
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import Redis from 'ioredis';
|
||||
import * as Redis from 'ioredis';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import type { NotesRepository, RolesRepository } from '@/models/index.js';
|
||||
import { QueryService } from '@/core/QueryService.js';
|
||||
@@ -65,12 +65,15 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const role = await this.rolesRepository.findOneBy({
|
||||
id: ps.roleId,
|
||||
isPublic: true,
|
||||
});
|
||||
|
||||
if (role == null) {
|
||||
throw new ApiError(meta.errors.noSuchRole);
|
||||
}
|
||||
|
||||
if (!role.isExplorable) {
|
||||
return [];
|
||||
}
|
||||
const limit = ps.limit + (ps.untilId ? 1 : 0) + (ps.sinceId ? 1 : 0); // untilIdに指定したものも含まれるため+1
|
||||
const noteIdsRes = await this.redisClient.xrevrange(
|
||||
`roleTimeline:${role.id}`,
|
||||
|
@@ -4,6 +4,7 @@ import type { UsedUsernamesRepository, UsersRepository } from '@/models/index.js
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import { localUsernameSchema } from '@/models/entities/User.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { MetaService } from '@/core/MetaService.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['users'],
|
||||
@@ -39,9 +40,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
|
||||
@Inject(DI.usedUsernamesRepository)
|
||||
private usedUsernamesRepository: UsedUsernamesRepository,
|
||||
|
||||
private metaService: MetaService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
// Get exist
|
||||
const exist = await this.usersRepository.countBy({
|
||||
host: IsNull(),
|
||||
usernameLower: ps.username.toLowerCase(),
|
||||
@@ -49,8 +51,11 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
|
||||
const exist2 = await this.usedUsernamesRepository.countBy({ username: ps.username.toLowerCase() });
|
||||
|
||||
const meta = await this.metaService.fetch();
|
||||
const isPreserved = meta.preservedUsernames.map(x => x.toLowerCase()).includes(ps.username.toLowerCase());
|
||||
|
||||
return {
|
||||
available: exist === 0 && exist2 === 0,
|
||||
available: exist === 0 && exist2 === 0 && !isPreserved,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
@@ -50,8 +50,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
private queryService: QueryService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const query = this.usersRepository.createQueryBuilder('user');
|
||||
query.where('user.isExplorable = TRUE');
|
||||
const query = this.usersRepository.createQueryBuilder('user')
|
||||
.where('user.isExplorable = TRUE')
|
||||
.andWhere('user.isSuspended = FALSE');
|
||||
|
||||
switch (ps.state) {
|
||||
case 'alive': query.andWhere('user.updatedAt > :date', { date: new Date(Date.now() - 1000 * 60 * 60 * 24 * 5) }); break;
|
||||
|
@@ -13,6 +13,8 @@ export const meta = {
|
||||
|
||||
requireCredential: true,
|
||||
|
||||
prohibitMoved: true,
|
||||
|
||||
kind: 'write:account',
|
||||
|
||||
description: 'Create a new list of users.',
|
||||
@@ -58,7 +60,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
if (currentCount > (await this.roleService.getUserPolicies(me.id)).userListLimit) {
|
||||
throw new ApiError(meta.errors.tooManyUserLists);
|
||||
}
|
||||
|
||||
|
||||
const userList = await this.userListsRepository.insert({
|
||||
id: this.idService.genId(),
|
||||
createdAt: new Date(),
|
||||
|
@@ -12,6 +12,8 @@ export const meta = {
|
||||
|
||||
requireCredential: true,
|
||||
|
||||
prohibitMoved: true,
|
||||
|
||||
kind: 'write:account',
|
||||
|
||||
description: 'Remove a user from a list.',
|
||||
|
@@ -12,6 +12,8 @@ export const meta = {
|
||||
|
||||
requireCredential: true,
|
||||
|
||||
prohibitMoved: true,
|
||||
|
||||
kind: 'write:account',
|
||||
|
||||
description: 'Add a user to an existing list.',
|
||||
|
@@ -0,0 +1,85 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import { IdService } from '@/core/IdService.js';
|
||||
import type { UserMemoRepository } from '@/models/index.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { GetterService } from '@/server/api/GetterService.js';
|
||||
import { ApiError } from '../../error.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['account'],
|
||||
|
||||
requireCredential: true,
|
||||
|
||||
kind: 'write:account',
|
||||
|
||||
errors: {
|
||||
noSuchUser: {
|
||||
message: 'No such user.',
|
||||
code: 'NO_SUCH_USER',
|
||||
id: '6fef56f3-e765-4957-88e5-c6f65329b8a5',
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
||||
export const paramDef = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
userId: { type: 'string', format: 'misskey:id' },
|
||||
memo: {
|
||||
type: 'string',
|
||||
nullable: true,
|
||||
description: 'A personal memo for the target user. If null or empty, delete the memo.',
|
||||
},
|
||||
},
|
||||
required: ['userId', 'memo'],
|
||||
} as const;
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
@Injectable()
|
||||
export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
constructor(
|
||||
@Inject(DI.userMemosRepository)
|
||||
private userMemosRepository: UserMemoRepository,
|
||||
private getterService: GetterService,
|
||||
private idService: IdService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
// Get target
|
||||
const target = await this.getterService.getUser(ps.userId).catch(err => {
|
||||
if (err.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser);
|
||||
throw err;
|
||||
});
|
||||
|
||||
// 引数がnullか空文字であれば、パーソナルメモを削除する
|
||||
if (ps.memo === '' || ps.memo == null) {
|
||||
await this.userMemosRepository.delete({
|
||||
userId: me.id,
|
||||
targetUserId: target.id,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// 以前に作成されたパーソナルメモがあるかどうか確認
|
||||
const previousMemo = await this.userMemosRepository.findOneBy({
|
||||
userId: me.id,
|
||||
targetUserId: target.id,
|
||||
});
|
||||
|
||||
if (!previousMemo) {
|
||||
await this.userMemosRepository.insert({
|
||||
id: this.idService.genId(),
|
||||
userId: me.id,
|
||||
targetUserId: target.id,
|
||||
memo: ps.memo,
|
||||
});
|
||||
} else {
|
||||
await this.userMemosRepository.update(previousMemo.id, {
|
||||
userId: me.id,
|
||||
targetUserId: target.id,
|
||||
memo: ps.memo,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
@@ -70,10 +70,10 @@ export class UrlPreviewService {
|
||||
await summaly(url, {
|
||||
followRedirects: false,
|
||||
lang: lang ?? 'ja-JP',
|
||||
agent: {
|
||||
agent: this.config.proxy ? {
|
||||
http: this.httpRequestService.httpAgent,
|
||||
https: this.httpRequestService.httpsAgent,
|
||||
},
|
||||
} : undefined,
|
||||
});
|
||||
|
||||
this.logger.succ(`Got preview of ${url}: ${summary.title}`);
|
||||
|
@@ -36,7 +36,7 @@ html
|
||||
link(rel='prefetch' href='https://xn--931a.moe/assets/not-found.jpg')
|
||||
link(rel='prefetch' href='https://xn--931a.moe/assets/error.jpg')
|
||||
//- https://github.com/misskey-dev/misskey/issues/9842
|
||||
link(rel='stylesheet' href='/assets/tabler-icons/tabler-icons.min.css?v2.12.0')
|
||||
link(rel='stylesheet' href='/assets/tabler-icons/tabler-icons.min.css?v2.17.0')
|
||||
link(rel='modulepreload' href=`/vite/${clientEntry.file}`)
|
||||
|
||||
if !config.clientManifestExists
|
||||
|
Reference in New Issue
Block a user