summaryrefslogtreecommitdiff
path: root/packages/backend/src/core
diff options
context:
space:
mode:
Diffstat (limited to 'packages/backend/src/core')
-rw-r--r--packages/backend/src/core/AchievementService.ts114
-rw-r--r--packages/backend/src/core/CoreModule.ts6
-rw-r--r--packages/backend/src/core/entities/NotificationEntityService.ts3
-rw-r--r--packages/backend/src/core/entities/UserEntityService.ts7
4 files changed, 128 insertions, 2 deletions
diff --git a/packages/backend/src/core/AchievementService.ts b/packages/backend/src/core/AchievementService.ts
new file mode 100644
index 0000000000..4ed75308eb
--- /dev/null
+++ b/packages/backend/src/core/AchievementService.ts
@@ -0,0 +1,114 @@
+import { Inject, Injectable } from '@nestjs/common';
+import type { UserProfilesRepository, UsersRepository } from '@/models/index.js';
+import type { User } from '@/models/entities/User.js';
+import { DI } from '@/di-symbols.js';
+import { bindThis } from '@/decorators.js';
+import { CreateNotificationService } from '@/core/CreateNotificationService.js';
+
+const ACHIEVEMENT_TYPES = [
+ 'notes1',
+ 'notes10',
+ 'notes100',
+ 'notes500',
+ 'notes1000',
+ 'notes5000',
+ 'notes10000',
+ 'notes20000',
+ 'notes30000',
+ 'notes40000',
+ 'notes50000',
+ 'notes60000',
+ 'notes70000',
+ 'notes80000',
+ 'notes90000',
+ 'notes100000',
+ 'login3',
+ 'login7',
+ 'login15',
+ 'login30',
+ 'login60',
+ 'login100',
+ 'login200',
+ 'login300',
+ 'login400',
+ 'login500',
+ 'login600',
+ 'login700',
+ 'login800',
+ 'login900',
+ 'login1000',
+ 'passedSinceAccountCreated1',
+ 'passedSinceAccountCreated2',
+ 'passedSinceAccountCreated3',
+ 'loggedInOnBirthday',
+ 'noteClipped1',
+ 'noteFavorited1',
+ 'profileFilled',
+ 'markedAsCat',
+ 'following1',
+ 'following10',
+ 'following50',
+ 'following100',
+ 'following300',
+ 'followers1',
+ 'followers10',
+ 'followers50',
+ 'followers100',
+ 'followers300',
+ 'followers500',
+ 'followers1000',
+ 'collectAchievements30',
+ 'iLoveMisskey',
+ 'client30min',
+ 'noteDeletedWithin1min',
+ 'postedAtLateNight',
+ 'postedAt0min0sec',
+ 'selfQuote',
+ 'htl20npm',
+ 'driveFolderCircularReference',
+ 'reactWithoutRead',
+ 'clickedClickHere',
+ 'justPlainLucky',
+ 'setNameToSyuilo',
+ 'cookieClicked',
+ 'brainDiver',
+] as const;
+
+@Injectable()
+export class AchievementService {
+ constructor(
+ @Inject(DI.usersRepository)
+ private usersRepository: UsersRepository,
+
+ @Inject(DI.userProfilesRepository)
+ private userProfilesRepository: UserProfilesRepository,
+
+ private createNotificationService: CreateNotificationService,
+ ) {
+ }
+
+ @bindThis
+ public async create(
+ userId: User['id'],
+ type: string,
+ ): Promise<void> {
+ if (!ACHIEVEMENT_TYPES.includes(type)) return;
+
+ const date = Date.now();
+
+ const profile = await this.userProfilesRepository.findOneByOrFail({ userId: userId });
+
+ if (profile.achievements.some(a => a.name === type)) return;
+
+ await this.userProfilesRepository.update(userId, {
+ achievements: [...profile.achievements, {
+ name: type,
+ unlockedAt: date,
+ }],
+ });
+
+ this.createNotificationService.createNotification(userId, 'achievementEarned', {
+ achievement: type,
+ });
+ }
+}
diff --git a/packages/backend/src/core/CoreModule.ts b/packages/backend/src/core/CoreModule.ts
index 0ae1ee32b2..eddf407940 100644
--- a/packages/backend/src/core/CoreModule.ts
+++ b/packages/backend/src/core/CoreModule.ts
@@ -4,6 +4,7 @@ import { AccountUpdateService } from './AccountUpdateService.js';
import { AiService } from './AiService.js';
import { AntennaService } from './AntennaService.js';
import { AppLockService } from './AppLockService.js';
+import { AchievementService } from './AchievementService.js';
import { CaptchaService } from './CaptchaService.js';
import { CreateNotificationService } from './CreateNotificationService.js';
import { CreateSystemUserService } from './CreateSystemUserService.js';
@@ -128,6 +129,7 @@ const $AccountUpdateService: Provider = { provide: 'AccountUpdateService', useEx
const $AiService: Provider = { provide: 'AiService', useExisting: AiService };
const $AntennaService: Provider = { provide: 'AntennaService', useExisting: AntennaService };
const $AppLockService: Provider = { provide: 'AppLockService', useExisting: AppLockService };
+const $AchievementService: Provider = { provide: 'AchievementService', useExisting: AchievementService };
const $CaptchaService: Provider = { provide: 'CaptchaService', useExisting: CaptchaService };
const $CreateNotificationService: Provider = { provide: 'CreateNotificationService', useExisting: CreateNotificationService };
const $CreateSystemUserService: Provider = { provide: 'CreateSystemUserService', useExisting: CreateSystemUserService };
@@ -255,6 +257,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
AiService,
AntennaService,
AppLockService,
+ AchievementService,
CaptchaService,
CreateNotificationService,
CreateSystemUserService,
@@ -376,6 +379,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
$AiService,
$AntennaService,
$AppLockService,
+ $AchievementService,
$CaptchaService,
$CreateNotificationService,
$CreateSystemUserService,
@@ -498,6 +502,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
AiService,
AntennaService,
AppLockService,
+ AchievementService,
CaptchaService,
CreateNotificationService,
CreateSystemUserService,
@@ -618,6 +623,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
$AiService,
$AntennaService,
$AppLockService,
+ $AchievementService,
$CaptchaService,
$CreateNotificationService,
$CreateSystemUserService,
diff --git a/packages/backend/src/core/entities/NotificationEntityService.ts b/packages/backend/src/core/entities/NotificationEntityService.ts
index a1c2c9cffb..a8210eea02 100644
--- a/packages/backend/src/core/entities/NotificationEntityService.ts
+++ b/packages/backend/src/core/entities/NotificationEntityService.ts
@@ -114,6 +114,9 @@ export class NotificationEntityService implements OnModuleInit {
...(notification.type === 'groupInvited' ? {
invitation: this.userGroupInvitationEntityService.pack(notification.userGroupInvitationId!),
} : {}),
+ ...(notification.type === 'achievementEarned' ? {
+ achievement: notification.achievement,
+ } : {}),
...(notification.type === 'app' ? {
body: notification.customBody,
header: notification.customHeader ?? token?.name,
diff --git a/packages/backend/src/core/entities/UserEntityService.ts b/packages/backend/src/core/entities/UserEntityService.ts
index bf6f6f4553..34b523e143 100644
--- a/packages/backend/src/core/entities/UserEntityService.ts
+++ b/packages/backend/src/core/entities/UserEntityService.ts
@@ -12,7 +12,7 @@ import { Cache } from '@/misc/cache.js';
import type { Instance } from '@/models/entities/Instance.js';
import type { ILocalUser, IRemoteUser, User } from '@/models/entities/User.js';
import { birthdaySchema, descriptionSchema, localUsernameSchema, locationSchema, nameSchema, passwordSchema } from '@/models/entities/User.js';
-import type { UsersRepository, UserSecurityKeysRepository, FollowingsRepository, FollowRequestsRepository, BlockingsRepository, MutingsRepository, DriveFilesRepository, NoteUnreadsRepository, ChannelFollowingsRepository, NotificationsRepository, UserNotePiningsRepository, UserProfilesRepository, InstancesRepository, AnnouncementReadsRepository, MessagingMessagesRepository, UserGroupJoiningsRepository, AnnouncementsRepository, AntennaNotesRepository, PagesRepository } from '@/models/index.js';
+import type { UsersRepository, UserSecurityKeysRepository, FollowingsRepository, FollowRequestsRepository, BlockingsRepository, MutingsRepository, DriveFilesRepository, NoteUnreadsRepository, ChannelFollowingsRepository, NotificationsRepository, UserNotePiningsRepository, UserProfilesRepository, InstancesRepository, AnnouncementReadsRepository, MessagingMessagesRepository, UserGroupJoiningsRepository, AnnouncementsRepository, AntennaNotesRepository, PagesRepository, UserProfile } from '@/models/index.js';
import { bindThis } from '@/decorators.js';
import { RoleService } from '@/core/RoleService.js';
import type { OnModuleInit } from '@nestjs/common';
@@ -343,6 +343,7 @@ export class UserEntityService implements OnModuleInit {
options?: {
detail?: D,
includeSecrets?: boolean,
+ userProfile?: UserProfile,
},
): Promise<IsMeAndIsUserDetailed<ExpectsMe, D>> {
const opts = Object.assign({
@@ -375,7 +376,7 @@ export class UserEntityService implements OnModuleInit {
.innerJoinAndSelect('pin.note', 'note')
.orderBy('pin.id', 'DESC')
.getMany() : [];
- const profile = opts.detail ? await this.userProfilesRepository.findOneByOrFail({ userId: user.id }) : null;
+ 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 :
@@ -493,6 +494,8 @@ export class UserEntityService implements OnModuleInit {
mutingNotificationTypes: profile!.mutingNotificationTypes,
emailNotificationTypes: profile!.emailNotificationTypes,
showTimelineReplies: user.showTimelineReplies ?? falsy,
+ achievements: profile!.achievements,
+ loggedInDays: profile!.loggedInDates.length,
} : {}),
...(opts.includeSecrets ? {