Merge remote-tracking branch 'misskey-original/develop' into develop

# Conflicts:
#	README.md
#	packages/frontend/src/components/MkMenu.vue
#	packages/frontend/src/components/MkNote.vue
#	packages/frontend/src/components/MkNoteSimple.vue
#	packages/frontend/src/components/MkPostForm.vue
#	packages/frontend/src/components/MkSignupDialog.form.vue
#	packages/frontend/src/pages/custom-emojis-manager.vue
#	packages/frontend/src/pages/settings/general.vue
#	packages/frontend/src/pages/timeline.vue
#	packages/frontend/src/pages/user/home.vue
#	packages/frontend/src/widgets/WidgetInstanceInfo.vue
This commit is contained in:
mattyatea 2023-12-23 09:14:17 +09:00
commit 487b3089a0
315 changed files with 4855 additions and 1825 deletions

View file

@ -77,6 +77,17 @@ export class FeaturedService {
return Array.from(ranking.keys());
}
@bindThis
private async removeFromRanking(name: string, windowRange: number, element: string): Promise<void> {
const currentWindow = this.getCurrentWindow(windowRange);
const previousWindow = currentWindow - 1;
const redisPipeline = this.redisClient.pipeline();
redisPipeline.zrem(`${name}:${currentWindow}`, element);
redisPipeline.zrem(`${name}:${previousWindow}`, element);
await redisPipeline.exec();
}
@bindThis
public updateGlobalNotesRanking(noteId: MiNote['id'], score = 1): Promise<void> {
return this.updateRankingOf('featuredGlobalNotesRanking', GLOBAL_NOTES_RANKING_WINDOW, noteId, score);
@ -126,4 +137,9 @@ export class FeaturedService {
public getHashtagsRanking(threshold: number): Promise<string[]> {
return this.getRankingOf('featuredHashtagsRanking', HASHTAG_RANKING_WINDOW, threshold);
}
@bindThis
public removeHashtagsFromRanking(hashtag: string): Promise<void> {
return this.removeFromRanking('featuredHashtagsRanking', HASHTAG_RANKING_WINDOW, hashtag);
}
}

View file

@ -11,6 +11,7 @@ import { MiMeta } from '@/models/Meta.js';
import { GlobalEventService } from '@/core/GlobalEventService.js';
import { bindThis } from '@/decorators.js';
import type { GlobalEvents } from '@/core/GlobalEventService.js';
import { FeaturedService } from '@/core/FeaturedService.js';
import type { OnApplicationShutdown } from '@nestjs/common';
@Injectable()
@ -25,6 +26,7 @@ export class MetaService implements OnApplicationShutdown {
@Inject(DI.db)
private db: DataSource,
private featuredService: FeaturedService,
private globalEventService: GlobalEventService,
) {
//this.onMessage = this.onMessage.bind(this);
@ -95,6 +97,8 @@ export class MetaService implements OnApplicationShutdown {
@bindThis
public async update(data: Partial<MiMeta>): Promise<MiMeta> {
let before: MiMeta | undefined;
const updated = await this.db.transaction(async transactionalEntityManager => {
const metas = await transactionalEntityManager.find(MiMeta, {
order: {
@ -102,10 +106,10 @@ export class MetaService implements OnApplicationShutdown {
},
});
const meta = metas[0];
before = metas[0];
if (meta) {
await transactionalEntityManager.update(MiMeta, meta.id, data);
if (before) {
await transactionalEntityManager.update(MiMeta, before.id, data);
const metas = await transactionalEntityManager.find(MiMeta, {
order: {
@ -119,6 +123,21 @@ export class MetaService implements OnApplicationShutdown {
}
});
if (data.hiddenTags) {
process.nextTick(() => {
const hiddenTags = new Set<string>(data.hiddenTags);
if (before) {
for (const previousHiddenTag of before.hiddenTags) {
hiddenTags.delete(previousHiddenTag);
}
}
for (const hiddenTag of hiddenTags) {
this.featuredService.removeHashtagsFromRanking(hiddenTag);
}
});
}
this.globalEventService.publishInternalEvent('metaUpdated', updated);
return updated;

View file

@ -262,7 +262,7 @@ export class NoteCreateService implements OnApplicationShutdown {
}
// Check blocking
if (data.renote && data.text == null && data.poll == null && (data.files == null || data.files.length === 0)) {
if (this.isQuote(data)) {
if (data.renote.userHost === null) {
if (data.renote.userId !== user.id) {
const blocked = await this.userBlockingService.checkBlocked(data.renote.userId, user.id);
@ -590,7 +590,7 @@ export class NoteCreateService implements OnApplicationShutdown {
// If it is renote
if (data.renote) {
const type = data.text ? 'quote' : 'renote';
const type = this.isQuote(data) ? 'quote' : 'renote';
// Notify
if (data.renote.userHost === null) {
@ -697,6 +697,12 @@ export class NoteCreateService implements OnApplicationShutdown {
return false;
}
@bindThis
private isQuote(note: Option): note is Option & { renote: MiNote } {
// sync with misc/is-quote.ts
return !!note.renote && (!!note.text || !!note.cw || (!!note.files && !!note.files.length) || !!note.poll);
}
@bindThis
private incRenoteCount(renote: MiNote) {
this.notesRepository.createQueryBuilder().update()
@ -762,7 +768,7 @@ export class NoteCreateService implements OnApplicationShutdown {
private async renderNoteOrRenoteActivity(data: Option, note: MiNote) {
if (data.localOnly) return null;
const content = data.renote && data.text == null && data.poll == null && (data.files == null || data.files.length === 0)
const content = data.renote && !this.isQuote(data)
? this.apRendererService.renderAnnounce(data.renote.uri ? data.renote.uri : `${this.config.url}/notes/${data.renote.id}`, note)
: this.apRendererService.renderCreate(await this.apRendererService.renderNote(note, false), note);

View file

@ -6,7 +6,14 @@
import { Inject, Injectable } from '@nestjs/common';
import * as Redis from 'ioredis';
import { In } from 'typeorm';
import type { MiRole, MiRoleAssignment, RoleAssignmentsRepository, RolesRepository, UsersRepository } from '@/models/_.js';
import { ModuleRef } from '@nestjs/core';
import type {
MiRole,
MiRoleAssignment,
RoleAssignmentsRepository,
RolesRepository,
UsersRepository,
} from '@/models/_.js';
import { MemoryKVCache, MemorySingleCache } from '@/misc/cache.js';
import type { MiUser } from '@/models/User.js';
import { DI } from '@/di-symbols.js';
@ -16,12 +23,13 @@ import { CacheService } from '@/core/CacheService.js';
import type { RoleCondFormulaValue } from '@/models/Role.js';
import { UserEntityService } from '@/core/entities/UserEntityService.js';
import type { GlobalEvents } from '@/core/GlobalEventService.js';
import { IdService } from '@/core/IdService.js';
import { GlobalEventService } from '@/core/GlobalEventService.js';
import { IdService } from '@/core/IdService.js';
import { ModerationLogService } from '@/core/ModerationLogService.js';
import type { Packed } from '@/misc/json-schema.js';
import { FanoutTimelineService } from '@/core/FanoutTimelineService.js';
import type { OnApplicationShutdown } from '@nestjs/common';
import { NotificationService } from '@/core/NotificationService.js';
import type { OnApplicationShutdown, OnModuleInit } from '@nestjs/common';
export type RolePolicies = {
gtlAvailable: boolean;
@ -84,14 +92,17 @@ export const DEFAULT_POLICIES: RolePolicies = {
};
@Injectable()
export class RoleService implements OnApplicationShutdown {
export class RoleService implements OnApplicationShutdown, OnModuleInit {
private rolesCache: MemorySingleCache<MiRole[]>;
private roleAssignmentByUserIdCache: MemoryKVCache<MiRoleAssignment[]>;
private notificationService: NotificationService;
public static AlreadyAssignedError = class extends Error {};
public static NotAssignedError = class extends Error {};
constructor(
private moduleRef: ModuleRef,
@Inject(DI.redis)
private redisClient: Redis.Redis,
@ -126,6 +137,10 @@ export class RoleService implements OnApplicationShutdown {
this.redisForSub.on('message', this.onMessage);
}
async onModuleInit() {
this.notificationService = this.moduleRef.get(NotificationService.name);
}
@bindThis
private async onMessage(_: string, data: string): Promise<void> {
const obj = JSON.parse(data);
@ -436,6 +451,12 @@ export class RoleService implements OnApplicationShutdown {
this.globalEventService.publishInternalEvent('userRoleAssigned', created);
if (role.isPublic) {
this.notificationService.createNotification(userId, 'roleAssigned', {
roleId: roleId,
});
}
if (moderator) {
const user = await this.usersRepository.findOneByOrFail({ id: userId });
this.moderationLogService.log(moderator, 'assignRole', {

View file

@ -3,30 +3,34 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { Inject, Injectable, OnApplicationShutdown } from '@nestjs/common';
import { Inject, Injectable, OnApplicationShutdown, OnModuleInit } from '@nestjs/common';
import * as Redis from 'ioredis';
import { ModuleRef } from '@nestjs/core';
import type { UserListMembershipsRepository } from '@/models/_.js';
import type { MiUser } from '@/models/User.js';
import type { MiUserList } from '@/models/UserList.js';
import type { MiUserListMembership } from '@/models/UserListMembership.js';
import { IdService } from '@/core/IdService.js';
import type { GlobalEvents } from '@/core/GlobalEventService.js';
import { GlobalEventService } from '@/core/GlobalEventService.js';
import { DI } from '@/di-symbols.js';
import { UserEntityService } from '@/core/entities/UserEntityService.js';
import { ProxyAccountService } from '@/core/ProxyAccountService.js';
import { bindThis } from '@/decorators.js';
import { RoleService } from '@/core/RoleService.js';
import { QueueService } from '@/core/QueueService.js';
import { RedisKVCache } from '@/misc/cache.js';
import type { GlobalEvents } from '@/core/GlobalEventService.js';
import { RoleService } from '@/core/RoleService.js';
@Injectable()
export class UserListService implements OnApplicationShutdown {
export class UserListService implements OnApplicationShutdown, OnModuleInit {
public static TooManyUsersError = class extends Error {};
public membersCache: RedisKVCache<Set<string>>;
private roleService: RoleService;
constructor(
private moduleRef: ModuleRef,
@Inject(DI.redis)
private redisClient: Redis.Redis,
@ -38,7 +42,6 @@ export class UserListService implements OnApplicationShutdown {
private userEntityService: UserEntityService,
private idService: IdService,
private roleService: RoleService,
private globalEventService: GlobalEventService,
private proxyAccountService: ProxyAccountService,
private queueService: QueueService,
@ -54,6 +57,10 @@ export class UserListService implements OnApplicationShutdown {
this.redisForSub.on('message', this.onMessage);
}
async onModuleInit() {
this.roleService = this.moduleRef.get(RoleService.name);
}
@bindThis
private async onMessage(_: string, data: string): Promise<void> {
const obj = JSON.parse(data);

View file

@ -15,8 +15,8 @@ import type { Packed } from '@/misc/json-schema.js';
import { bindThis } from '@/decorators.js';
import { isNotNull } from '@/misc/is-not-null.js';
import { FilterUnionByProperty, notificationTypes } from '@/types.js';
import { RoleEntityService } from './RoleEntityService.js';
import type { OnModuleInit } from '@nestjs/common';
import type { CustomEmojiService } from '../CustomEmojiService.js';
import type { UserEntityService } from './UserEntityService.js';
import type { NoteEntityService } from './NoteEntityService.js';
@ -27,7 +27,7 @@ const NOTE_REQUIRED_GROUPED_NOTIFICATION_TYPES = new Set(['note', 'mention', 're
export class NotificationEntityService implements OnModuleInit {
private userEntityService: UserEntityService;
private noteEntityService: NoteEntityService;
private customEmojiService: CustomEmojiService;
private roleEntityService: RoleEntityService;
constructor(
private moduleRef: ModuleRef,
@ -43,14 +43,13 @@ export class NotificationEntityService implements OnModuleInit {
//private userEntityService: UserEntityService,
//private noteEntityService: NoteEntityService,
//private customEmojiService: CustomEmojiService,
) {
}
onModuleInit() {
this.userEntityService = this.moduleRef.get('UserEntityService');
this.noteEntityService = this.moduleRef.get('NoteEntityService');
this.customEmojiService = this.moduleRef.get('CustomEmojiService');
this.roleEntityService = this.moduleRef.get('RoleEntityService');
}
@bindThis
@ -81,6 +80,7 @@ export class NotificationEntityService implements OnModuleInit {
detail: false,
})
) : undefined;
const role = notification.type === 'roleAssigned' ? await this.roleEntityService.pack(notification.roleId) : undefined;
return await awaitAll({
id: notification.id,
@ -92,6 +92,9 @@ export class NotificationEntityService implements OnModuleInit {
...(notification.type === 'reaction' ? {
reaction: notification.reaction,
} : {}),
...(notification.type === 'roleAssigned' ? {
role: role,
} : {}),
...(notification.type === 'achievementEarned' ? {
achievement: notification.achievement,
} : {}),
@ -216,6 +219,8 @@ export class NotificationEntityService implements OnModuleInit {
});
}
const role = notification.type === 'roleAssigned' ? await this.roleEntityService.pack(notification.roleId) : undefined;
return await awaitAll({
id: notification.id,
createdAt: new Date(notification.createdAt).toISOString(),
@ -226,6 +231,9 @@ export class NotificationEntityService implements OnModuleInit {
...(notification.type === 'reaction' ? {
reaction: notification.reaction,
} : {}),
...(notification.type === 'roleAssigned' ? {
role: role,
} : {}),
...(notification.type === 'achievementEarned' ? {
achievement: notification.achievement,
} : {}),

View file

@ -332,13 +332,13 @@ export class UserEntityService implements OnModuleInit {
const profile = opts.detail ? (opts.userProfile ?? await this.userProfilesRepository.findOneByOrFail({ userId: user.id })) : null;
const followingCount = profile == null ? null :
(profile.ffVisibility === 'public') || isMe ? user.followingCount :
(profile.ffVisibility === 'followers') && (relation && relation.isFollowing) ? user.followingCount :
(profile.followingVisibility === 'public') || isMe ? user.followingCount :
(profile.followingVisibility === 'followers') && (relation && relation.isFollowing) ? user.followingCount :
null;
const followersCount = profile == null ? null :
(profile.ffVisibility === 'public') || isMe ? user.followersCount :
(profile.ffVisibility === 'followers') && (relation && relation.isFollowing) ? user.followersCount :
(profile.followersVisibility === 'public') || isMe ? user.followersCount :
(profile.followersVisibility === 'followers') && (relation && relation.isFollowing) ? user.followersCount :
null;
const isModerator = isMe && opts.detail ? this.roleService.isModerator(user) : null;
@ -418,7 +418,8 @@ export class UserEntityService implements OnModuleInit {
pinnedPageId: profile!.pinnedPageId,
pinnedPage: profile!.pinnedPageId ? this.pageEntityService.pack(profile!.pinnedPageId, me) : null,
publicReactions: profile!.publicReactions,
ffVisibility: profile!.ffVisibility,
followersVisibility: profile!.followersVisibility,
followingVisibility: profile!.followingVisibility,
twoFactorEnabled: profile!.twoFactorEnabled,
usePasswordLessLogin: profile!.usePasswordLessLogin,
securityKeys: profile!.twoFactorEnabled