Merge branch 'develop' of https://github.com/misskey-dev/misskey into develop
This commit is contained in:
commit
62b6c4d09b
7 changed files with 82 additions and 38 deletions
|
@ -26,10 +26,11 @@
|
||||||
-
|
-
|
||||||
|
|
||||||
### Server
|
### Server
|
||||||
|
- フォローインポートなどでの大量のフォロー等操作をキューイングするように #10544 @nmkj-io
|
||||||
- Misskey Webでのサーバーサイドエラー画面を改善
|
- Misskey Webでのサーバーサイドエラー画面を改善
|
||||||
- Misskey Webでのサーバーサイドエラーのログが残るように
|
- Misskey Webでのサーバーサイドエラーのログが残るように
|
||||||
- ノート作成時のアンテナ追加パフォーマンスを改善
|
- ノート作成時のアンテナ追加パフォーマンスを改善
|
||||||
- フォローインポートなどでの大量のフォロー等操作をキューイングするように #10544 @nmkj-io
|
- アンテナとロールTLのuntil/sinceプロパティが動くように
|
||||||
|
|
||||||
## 13.11.2
|
## 13.11.2
|
||||||
|
|
||||||
|
|
|
@ -13,6 +13,7 @@ import { MemoryKVCache, RedisSingleCache } from '@/misc/cache.js';
|
||||||
import { UtilityService } from '@/core/UtilityService.js';
|
import { UtilityService } from '@/core/UtilityService.js';
|
||||||
import type { Config } from '@/config.js';
|
import type { Config } from '@/config.js';
|
||||||
import { query } from '@/misc/prelude/url.js';
|
import { query } from '@/misc/prelude/url.js';
|
||||||
|
import type { Serialized } from '@/server/api/stream/types.js';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class CustomEmojiService {
|
export class CustomEmojiService {
|
||||||
|
@ -46,9 +47,9 @@ export class CustomEmojiService {
|
||||||
toRedisConverter: (value) => JSON.stringify(Array.from(value.values())),
|
toRedisConverter: (value) => JSON.stringify(Array.from(value.values())),
|
||||||
fromRedisConverter: (value) => {
|
fromRedisConverter: (value) => {
|
||||||
if (!Array.isArray(JSON.parse(value))) return undefined; // 古いバージョンの壊れたキャッシュが残っていることがある(そのうち消す)
|
if (!Array.isArray(JSON.parse(value))) return undefined; // 古いバージョンの壊れたキャッシュが残っていることがある(そのうち消す)
|
||||||
return new Map(JSON.parse(value).map((x) => [x.name, {
|
return new Map(JSON.parse(value).map((x: Serialized<Emoji>) => [x.name, {
|
||||||
...x,
|
...x,
|
||||||
updatedAt: new Date(x.updatedAt),
|
updatedAt: x.updatedAt && new Date(x.updatedAt),
|
||||||
}]));
|
}]));
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -20,6 +20,7 @@ import { bindThis } from '@/decorators.js';
|
||||||
import { UserBlockingService } from '@/core/UserBlockingService.js';
|
import { UserBlockingService } from '@/core/UserBlockingService.js';
|
||||||
import { MetaService } from '@/core/MetaService.js';
|
import { MetaService } from '@/core/MetaService.js';
|
||||||
import { CacheService } from '@/core/CacheService.js';
|
import { CacheService } from '@/core/CacheService.js';
|
||||||
|
import type { Config } from '@/config.js';
|
||||||
import Logger from '../logger.js';
|
import Logger from '../logger.js';
|
||||||
|
|
||||||
const logger = new Logger('following/create');
|
const logger = new Logger('following/create');
|
||||||
|
@ -44,6 +45,9 @@ export class UserFollowingService implements OnModuleInit {
|
||||||
constructor(
|
constructor(
|
||||||
private moduleRef: ModuleRef,
|
private moduleRef: ModuleRef,
|
||||||
|
|
||||||
|
@Inject(DI.config)
|
||||||
|
private config: Config,
|
||||||
|
|
||||||
@Inject(DI.usersRepository)
|
@Inject(DI.usersRepository)
|
||||||
private usersRepository: UsersRepository,
|
private usersRepository: UsersRepository,
|
||||||
|
|
||||||
|
@ -411,7 +415,7 @@ export class UserFollowingService implements OnModuleInit {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.userEntityService.isLocalUser(follower) && this.userEntityService.isRemoteUser(followee)) {
|
if (this.userEntityService.isLocalUser(follower) && this.userEntityService.isRemoteUser(followee)) {
|
||||||
const content = this.apRendererService.addContext(this.apRendererService.renderFollow(follower, followee));
|
const content = this.apRendererService.addContext(this.apRendererService.renderFollow(follower, followee, requestId ?? `${this.config.url}/follows/${followRequest.id}`));
|
||||||
this.queueService.deliver(follower, content, followee.inbox, false);
|
this.queueService.deliver(follower, content, followee.inbox, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@ import { Brackets, In, IsNull, LessThan, Not } from 'typeorm';
|
||||||
import accepts from 'accepts';
|
import accepts from 'accepts';
|
||||||
import vary from 'vary';
|
import vary from 'vary';
|
||||||
import { DI } from '@/di-symbols.js';
|
import { DI } from '@/di-symbols.js';
|
||||||
import type { FollowingsRepository, NotesRepository, EmojisRepository, NoteReactionsRepository, UserProfilesRepository, UserNotePiningsRepository, UsersRepository } from '@/models/index.js';
|
import type { FollowingsRepository, NotesRepository, EmojisRepository, NoteReactionsRepository, UserProfilesRepository, UserNotePiningsRepository, UsersRepository, FollowRequestsRepository } from '@/models/index.js';
|
||||||
import * as url from '@/misc/prelude/url.js';
|
import * as url from '@/misc/prelude/url.js';
|
||||||
import type { Config } from '@/config.js';
|
import type { Config } from '@/config.js';
|
||||||
import { ApRendererService } from '@/core/activitypub/ApRendererService.js';
|
import { ApRendererService } from '@/core/activitypub/ApRendererService.js';
|
||||||
|
@ -54,6 +54,9 @@ export class ActivityPubServerService {
|
||||||
@Inject(DI.followingsRepository)
|
@Inject(DI.followingsRepository)
|
||||||
private followingsRepository: FollowingsRepository,
|
private followingsRepository: FollowingsRepository,
|
||||||
|
|
||||||
|
@Inject(DI.followRequestsRepository)
|
||||||
|
private followRequestsRepository: FollowRequestsRepository,
|
||||||
|
|
||||||
private utilityService: UtilityService,
|
private utilityService: UtilityService,
|
||||||
private userEntityService: UserEntityService,
|
private userEntityService: UserEntityService,
|
||||||
private apRendererService: ApRendererService,
|
private apRendererService: ApRendererService,
|
||||||
|
@ -205,22 +208,22 @@ export class ActivityPubServerService {
|
||||||
reply.code(400);
|
reply.code(400);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const page = request.query.page === 'true';
|
const page = request.query.page === 'true';
|
||||||
|
|
||||||
const user = await this.usersRepository.findOneBy({
|
const user = await this.usersRepository.findOneBy({
|
||||||
id: userId,
|
id: userId,
|
||||||
host: IsNull(),
|
host: IsNull(),
|
||||||
});
|
});
|
||||||
|
|
||||||
if (user == null) {
|
if (user == null) {
|
||||||
reply.code(404);
|
reply.code(404);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
//#region Check ff visibility
|
//#region Check ff visibility
|
||||||
const profile = await this.userProfilesRepository.findOneByOrFail({ userId: user.id });
|
const profile = await this.userProfilesRepository.findOneByOrFail({ userId: user.id });
|
||||||
|
|
||||||
if (profile.ffVisibility === 'private') {
|
if (profile.ffVisibility === 'private') {
|
||||||
reply.code(403);
|
reply.code(403);
|
||||||
reply.header('Cache-Control', 'public, max-age=30');
|
reply.header('Cache-Control', 'public, max-age=30');
|
||||||
|
@ -231,31 +234,31 @@ export class ActivityPubServerService {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
//#endregion
|
//#endregion
|
||||||
|
|
||||||
const limit = 10;
|
const limit = 10;
|
||||||
const partOf = `${this.config.url}/users/${userId}/following`;
|
const partOf = `${this.config.url}/users/${userId}/following`;
|
||||||
|
|
||||||
if (page) {
|
if (page) {
|
||||||
const query = {
|
const query = {
|
||||||
followerId: user.id,
|
followerId: user.id,
|
||||||
} as FindOptionsWhere<Following>;
|
} as FindOptionsWhere<Following>;
|
||||||
|
|
||||||
// カーソルが指定されている場合
|
// カーソルが指定されている場合
|
||||||
if (cursor) {
|
if (cursor) {
|
||||||
query.id = LessThan(cursor);
|
query.id = LessThan(cursor);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get followings
|
// Get followings
|
||||||
const followings = await this.followingsRepository.find({
|
const followings = await this.followingsRepository.find({
|
||||||
where: query,
|
where: query,
|
||||||
take: limit + 1,
|
take: limit + 1,
|
||||||
order: { id: -1 },
|
order: { id: -1 },
|
||||||
});
|
});
|
||||||
|
|
||||||
// 「次のページ」があるかどうか
|
// 「次のページ」があるかどうか
|
||||||
const inStock = followings.length === limit + 1;
|
const inStock = followings.length === limit + 1;
|
||||||
if (inStock) followings.pop();
|
if (inStock) followings.pop();
|
||||||
|
|
||||||
const renderedFollowees = await Promise.all(followings.map(following => this.apRendererService.renderFollowUser(following.followeeId)));
|
const renderedFollowees = await Promise.all(followings.map(following => this.apRendererService.renderFollowUser(following.followeeId)));
|
||||||
const rendered = this.apRendererService.renderOrderedCollectionPage(
|
const rendered = this.apRendererService.renderOrderedCollectionPage(
|
||||||
`${partOf}?${url.query({
|
`${partOf}?${url.query({
|
||||||
|
@ -269,7 +272,7 @@ export class ActivityPubServerService {
|
||||||
cursor: followings[followings.length - 1].id,
|
cursor: followings[followings.length - 1].id,
|
||||||
})}` : undefined,
|
})}` : undefined,
|
||||||
);
|
);
|
||||||
|
|
||||||
this.setResponseType(request, reply);
|
this.setResponseType(request, reply);
|
||||||
return (this.apRendererService.addContext(rendered));
|
return (this.apRendererService.addContext(rendered));
|
||||||
} else {
|
} else {
|
||||||
|
@ -330,33 +333,33 @@ export class ActivityPubServerService {
|
||||||
reply.code(400);
|
reply.code(400);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const untilId = request.query.until_id;
|
const untilId = request.query.until_id;
|
||||||
if (untilId != null && typeof untilId !== 'string') {
|
if (untilId != null && typeof untilId !== 'string') {
|
||||||
reply.code(400);
|
reply.code(400);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const page = request.query.page === 'true';
|
const page = request.query.page === 'true';
|
||||||
|
|
||||||
if (countIf(x => x != null, [sinceId, untilId]) > 1) {
|
if (countIf(x => x != null, [sinceId, untilId]) > 1) {
|
||||||
reply.code(400);
|
reply.code(400);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const user = await this.usersRepository.findOneBy({
|
const user = await this.usersRepository.findOneBy({
|
||||||
id: userId,
|
id: userId,
|
||||||
host: IsNull(),
|
host: IsNull(),
|
||||||
});
|
});
|
||||||
|
|
||||||
if (user == null) {
|
if (user == null) {
|
||||||
reply.code(404);
|
reply.code(404);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const limit = 20;
|
const limit = 20;
|
||||||
const partOf = `${this.config.url}/users/${userId}/outbox`;
|
const partOf = `${this.config.url}/users/${userId}/outbox`;
|
||||||
|
|
||||||
if (page) {
|
if (page) {
|
||||||
const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'), sinceId, untilId)
|
const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'), sinceId, untilId)
|
||||||
.andWhere('note.userId = :userId', { userId: user.id })
|
.andWhere('note.userId = :userId', { userId: user.id })
|
||||||
|
@ -365,11 +368,11 @@ export class ActivityPubServerService {
|
||||||
.orWhere('note.visibility = \'home\'');
|
.orWhere('note.visibility = \'home\'');
|
||||||
}))
|
}))
|
||||||
.andWhere('note.localOnly = FALSE');
|
.andWhere('note.localOnly = FALSE');
|
||||||
|
|
||||||
const notes = await query.take(limit).getMany();
|
const notes = await query.take(limit).getMany();
|
||||||
|
|
||||||
if (sinceId) notes.reverse();
|
if (sinceId) notes.reverse();
|
||||||
|
|
||||||
const activities = await Promise.all(notes.map(note => this.packActivity(note)));
|
const activities = await Promise.all(notes.map(note => this.packActivity(note)));
|
||||||
const rendered = this.apRendererService.renderOrderedCollectionPage(
|
const rendered = this.apRendererService.renderOrderedCollectionPage(
|
||||||
`${partOf}?${url.query({
|
`${partOf}?${url.query({
|
||||||
|
@ -387,7 +390,7 @@ export class ActivityPubServerService {
|
||||||
until_id: notes[notes.length - 1].id,
|
until_id: notes[notes.length - 1].id,
|
||||||
})}` : undefined,
|
})}` : undefined,
|
||||||
);
|
);
|
||||||
|
|
||||||
this.setResponseType(request, reply);
|
this.setResponseType(request, reply);
|
||||||
return (this.apRendererService.addContext(rendered));
|
return (this.apRendererService.addContext(rendered));
|
||||||
} else {
|
} else {
|
||||||
|
@ -457,7 +460,7 @@ export class ActivityPubServerService {
|
||||||
// note
|
// note
|
||||||
fastify.get<{ Params: { note: string; } }>('/notes/:note', { constraints: { apOrHtml: 'ap' } }, async (request, reply) => {
|
fastify.get<{ Params: { note: string; } }>('/notes/:note', { constraints: { apOrHtml: 'ap' } }, async (request, reply) => {
|
||||||
vary(reply.raw, 'Accept');
|
vary(reply.raw, 'Accept');
|
||||||
|
|
||||||
const note = await this.notesRepository.findOneBy({
|
const note = await this.notesRepository.findOneBy({
|
||||||
id: request.params.note,
|
id: request.params.note,
|
||||||
visibility: In(['public', 'home']),
|
visibility: In(['public', 'home']),
|
||||||
|
@ -639,6 +642,41 @@ export class ActivityPubServerService {
|
||||||
return (this.apRendererService.addContext(this.apRendererService.renderFollow(follower, followee)));
|
return (this.apRendererService.addContext(this.apRendererService.renderFollow(follower, followee)));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// follow
|
||||||
|
fastify.get<{ Params: { followRequestId: string ; } }>('/follows/:followRequestId', async (request, reply) => {
|
||||||
|
// This may be used before the follow is completed, so we do not
|
||||||
|
// check if the following exists and only check if the follow request exists.
|
||||||
|
|
||||||
|
const followRequest = await this.followRequestsRepository.findOneBy({
|
||||||
|
id: request.params.followRequestId,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (followRequest == null) {
|
||||||
|
reply.code(404);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const [follower, followee] = await Promise.all([
|
||||||
|
this.usersRepository.findOneBy({
|
||||||
|
id: followRequest.followerId,
|
||||||
|
host: IsNull(),
|
||||||
|
}),
|
||||||
|
this.usersRepository.findOneBy({
|
||||||
|
id: followRequest.followeeId,
|
||||||
|
host: Not(IsNull()),
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (follower == null || followee == null) {
|
||||||
|
reply.code(404);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
reply.header('Cache-Control', 'public, max-age=180');
|
||||||
|
this.setResponseType(request, reply);
|
||||||
|
return (this.apRendererService.addContext(this.apRendererService.renderFollow(follower, followee)));
|
||||||
|
});
|
||||||
|
|
||||||
done();
|
done();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -76,18 +76,18 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||||
throw new ApiError(meta.errors.noSuchAntenna);
|
throw new ApiError(meta.errors.noSuchAntenna);
|
||||||
}
|
}
|
||||||
|
|
||||||
const limit = ps.limit + (ps.untilId ? 1 : 0); // untilIdに指定したものも含まれるため+1
|
const limit = ps.limit + (ps.untilId ? 1 : 0) + (ps.sinceId ? 1 : 0); // untilIdに指定したものも含まれるため+1
|
||||||
const noteIdsRes = await this.redisClient.xrevrange(
|
const noteIdsRes = await this.redisClient.xrevrange(
|
||||||
`antennaTimeline:${antenna.id}`,
|
`antennaTimeline:${antenna.id}`,
|
||||||
ps.untilId ? this.idService.parse(ps.untilId).date.getTime() : '+',
|
ps.untilId ? this.idService.parse(ps.untilId).date.getTime() : ps.untilDate ?? '+',
|
||||||
'-',
|
ps.sinceId ? this.idService.parse(ps.sinceId).date.getTime() : ps.sinceDate ?? '-',
|
||||||
'COUNT', limit);
|
'COUNT', limit);
|
||||||
|
|
||||||
if (noteIdsRes.length === 0) {
|
if (noteIdsRes.length === 0) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
const noteIds = noteIdsRes.map(x => x[1][1]).filter(x => x !== ps.untilId);
|
const noteIds = noteIdsRes.map(x => x[1][1]).filter(x => x !== ps.untilId && x !== ps.sinceId);
|
||||||
|
|
||||||
if (noteIds.length === 0) {
|
if (noteIds.length === 0) {
|
||||||
return [];
|
return [];
|
||||||
|
|
|
@ -71,18 +71,18 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||||
throw new ApiError(meta.errors.noSuchRole);
|
throw new ApiError(meta.errors.noSuchRole);
|
||||||
}
|
}
|
||||||
|
|
||||||
const limit = ps.limit + (ps.untilId ? 1 : 0); // untilIdに指定したものも含まれるため+1
|
const limit = ps.limit + (ps.untilId ? 1 : 0) + (ps.sinceId ? 1 : 0); // untilIdに指定したものも含まれるため+1
|
||||||
const noteIdsRes = await this.redisClient.xrevrange(
|
const noteIdsRes = await this.redisClient.xrevrange(
|
||||||
`roleTimeline:${role.id}`,
|
`roleTimeline:${role.id}`,
|
||||||
ps.untilId ? this.idService.parse(ps.untilId).date.getTime() : '+',
|
ps.untilId ? this.idService.parse(ps.untilId).date.getTime() : ps.untilDate ?? '+',
|
||||||
'-',
|
ps.sinceId ? this.idService.parse(ps.sinceId).date.getTime() : ps.sinceDate ?? '-',
|
||||||
'COUNT', limit);
|
'COUNT', limit);
|
||||||
|
|
||||||
if (noteIdsRes.length === 0) {
|
if (noteIdsRes.length === 0) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
const noteIds = noteIdsRes.map(x => x[1][1]).filter(x => x !== ps.untilId);
|
const noteIds = noteIdsRes.map(x => x[1][1]).filter(x => x !== ps.untilId && x !== ps.sinceId);
|
||||||
|
|
||||||
if (noteIds.length === 0) {
|
if (noteIds.length === 0) {
|
||||||
return [];
|
return [];
|
||||||
|
|
|
@ -172,7 +172,7 @@ type EventUnionFromDictionary<
|
||||||
> = U[keyof U];
|
> = U[keyof U];
|
||||||
|
|
||||||
// redis通すとDateのインスタンスはstringに変換されるので
|
// redis通すとDateのインスタンスはstringに変換されるので
|
||||||
type Serialized<T> = {
|
export type Serialized<T> = {
|
||||||
[K in keyof T]:
|
[K in keyof T]:
|
||||||
T[K] extends Date
|
T[K] extends Date
|
||||||
? string
|
? string
|
||||||
|
|
Loading…
Reference in a new issue