Merge tag '2023.12.0-beta.6' into merge-upstream
This commit is contained in:
commit
b3de794bdd
251 changed files with 2328 additions and 1176 deletions
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -293,7 +293,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 (data.renote && 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);
|
||||
|
@ -623,7 +623,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) {
|
||||
|
@ -730,6 +730,11 @@ export class NoteCreateService implements OnApplicationShutdown {
|
|||
return false;
|
||||
}
|
||||
|
||||
@bindThis
|
||||
private isQuote(note: Option): boolean {
|
||||
return !!note.text || !!note.cw || !!note.files || !!note.poll;
|
||||
}
|
||||
|
||||
@bindThis
|
||||
private incRenoteCount(renote: MiNote) {
|
||||
this.notesRepository.createQueryBuilder().update()
|
||||
|
@ -796,7 +801,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);
|
||||
|
||||
|
|
|
@ -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 { IdentifiableError } from '@/misc/identifiable-error.js';
|
||||
import type { MiUser } from '@/models/User.js';
|
||||
|
@ -17,12 +24,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;
|
||||
|
@ -85,17 +93,23 @@ 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;
|
||||
|
||||
constructor(
|
||||
@Inject(DI.redisForSub)
|
||||
private redisForSub: Redis.Redis,
|
||||
private moduleRef: ModuleRef,
|
||||
|
||||
@Inject(DI.redis)
|
||||
private redisClient: Redis.Redis,
|
||||
|
||||
@Inject(DI.redisForTimelines)
|
||||
private redisForTimelines: Redis.Redis,
|
||||
|
||||
@Inject(DI.redisForSub)
|
||||
private redisForSub: Redis.Redis,
|
||||
|
||||
@Inject(DI.usersRepository)
|
||||
private usersRepository: UsersRepository,
|
||||
|
||||
|
@ -121,6 +135,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);
|
||||
|
@ -427,6 +445,12 @@ export class RoleService implements OnApplicationShutdown {
|
|||
}).then(x => this.roleAssignmentsRepository.findOneByOrFail(x.identifiers[0]));
|
||||
|
||||
this.globalEventService.publishInternalEvent('userRoleAssigned', created);
|
||||
|
||||
if (role.isPublic) {
|
||||
this.notificationService.createNotification(userId, 'roleAssigned', {
|
||||
roleId: roleId,
|
||||
});
|
||||
}
|
||||
} else if (existing.expiresAt !== expiresAt) {
|
||||
await this.roleAssignmentsRepository.update(existing.id, {
|
||||
expiresAt: expiresAt,
|
||||
|
|
|
@ -10,15 +10,15 @@ 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 {
|
||||
|
|
|
@ -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,
|
||||
} : {}),
|
||||
|
@ -217,6 +220,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(),
|
||||
|
@ -227,6 +232,9 @@ export class NotificationEntityService implements OnModuleInit {
|
|||
...(notification.type === 'reaction' ? {
|
||||
reaction: notification.reaction,
|
||||
} : {}),
|
||||
...(notification.type === 'roleAssigned' ? {
|
||||
role: role,
|
||||
} : {}),
|
||||
...(notification.type === 'achievementEarned' ? {
|
||||
achievement: notification.achievement,
|
||||
} : {}),
|
||||
|
|
|
@ -334,13 +334,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;
|
||||
|
@ -417,7 +417,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
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue