diff options
Diffstat (limited to 'packages/backend/src/core')
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 ? { |