fix(backend): enumerate achievement types in some response schema (#15953)

* fix(backend): enumerate achievement types in some response schema

* refactor: use `ref`
This commit is contained in:
zyoshoka 2025-05-06 19:52:30 +09:00 committed by GitHub
parent 09317150e1
commit 3b676f39df
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 138 additions and 121 deletions

View File

@ -9,87 +9,7 @@ import type { MiUser } from '@/models/User.js';
import { DI } from '@/di-symbols.js'; import { DI } from '@/di-symbols.js';
import { bindThis } from '@/decorators.js'; import { bindThis } from '@/decorators.js';
import { NotificationService } from '@/core/NotificationService.js'; import { NotificationService } from '@/core/NotificationService.js';
import { ACHIEVEMENT_TYPES } from '@/models/UserProfile.js';
export 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',
'loggedInOnNewYearsDay',
'noteClipped1',
'noteFavorited1',
'myNoteFavorited1',
'profileFilled',
'markedAsCat',
'following1',
'following10',
'following50',
'following100',
'following300',
'followers1',
'followers10',
'followers50',
'followers100',
'followers300',
'followers500',
'followers1000',
'collectAchievements30',
'viewAchievements3min',
'iLoveMisskey',
'foundTreasure',
'client30min',
'client60min',
'noteDeletedWithin1min',
'postedAtLateNight',
'postedAt0min0sec',
'selfQuote',
'htl20npm',
'viewInstanceChart',
'outputHelloWorldOnScratchpad',
'open3windows',
'driveFolderCircularReference',
'reactWithoutRead',
'clickedClickHere',
'justPlainLucky',
'setNameToSyuilo',
'cookieClicked',
'brainDiver',
'smashTestNotificationButton',
'tutorialCompleted',
'bubbleGameExplodingHead',
'bubbleGameDoubleExplodingHead',
] as const;
@Injectable() @Injectable()
export class AchievementService { export class AchievementService {

View File

@ -67,6 +67,7 @@ import { packedChatMessageSchema, packedChatMessageLiteSchema, packedChatMessage
import { packedChatRoomSchema } from '@/models/json-schema/chat-room.js'; import { packedChatRoomSchema } from '@/models/json-schema/chat-room.js';
import { packedChatRoomInvitationSchema } from '@/models/json-schema/chat-room-invitation.js'; import { packedChatRoomInvitationSchema } from '@/models/json-schema/chat-room-invitation.js';
import { packedChatRoomMembershipSchema } from '@/models/json-schema/chat-room-membership.js'; import { packedChatRoomMembershipSchema } from '@/models/json-schema/chat-room-membership.js';
import { packedAchievementNameSchema, packedAchievementSchema } from '@/models/json-schema/achievement.js';
export const refs = { export const refs = {
UserLite: packedUserLiteSchema, UserLite: packedUserLiteSchema,
@ -78,6 +79,8 @@ export const refs = {
User: packedUserSchema, User: packedUserSchema,
UserList: packedUserListSchema, UserList: packedUserListSchema,
Achievement: packedAchievementSchema,
AchievementName: packedAchievementNameSchema,
Ad: packedAdSchema, Ad: packedAdSchema,
Announcement: packedAnnouncementSchema, Announcement: packedAnnouncementSchema,
App: packedAppSchema, App: packedAppSchema,

View File

@ -274,7 +274,7 @@ export class MiUserProfile {
default: [], default: [],
}) })
public achievements: { public achievements: {
name: string; name: typeof ACHIEVEMENT_TYPES[number];
unlockedAt: number; unlockedAt: number;
}[]; }[];
@ -295,3 +295,84 @@ export class MiUserProfile {
} }
} }
} }
export 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',
'loggedInOnNewYearsDay',
'noteClipped1',
'noteFavorited1',
'myNoteFavorited1',
'profileFilled',
'markedAsCat',
'following1',
'following10',
'following50',
'following100',
'following300',
'followers1',
'followers10',
'followers50',
'followers100',
'followers300',
'followers500',
'followers1000',
'collectAchievements30',
'viewAchievements3min',
'iLoveMisskey',
'foundTreasure',
'client30min',
'client60min',
'noteDeletedWithin1min',
'postedAtLateNight',
'postedAt0min0sec',
'selfQuote',
'htl20npm',
'viewInstanceChart',
'outputHelloWorldOnScratchpad',
'open3windows',
'driveFolderCircularReference',
'reactWithoutRead',
'clickedClickHere',
'justPlainLucky',
'setNameToSyuilo',
'cookieClicked',
'brainDiver',
'smashTestNotificationButton',
'tutorialCompleted',
'bubbleGameExplodingHead',
'bubbleGameDoubleExplodingHead',
] as const;

View File

@ -0,0 +1,25 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { ACHIEVEMENT_TYPES } from '@/models/UserProfile.js';
export const packedAchievementNameSchema = {
type: 'string',
enum: ACHIEVEMENT_TYPES,
optional: false,
} as const;
export const packedAchievementSchema = {
type: 'object',
properties: {
name: {
ref: 'AchievementName',
},
unlockedAt: {
type: 'number',
optional: false,
},
},
} as const;

View File

@ -3,7 +3,6 @@
* SPDX-License-Identifier: AGPL-3.0-only * SPDX-License-Identifier: AGPL-3.0-only
*/ */
import { ACHIEVEMENT_TYPES } from '@/core/AchievementService.js';
import { notificationTypes, userExportableEntities } from '@/types.js'; import { notificationTypes, userExportableEntities } from '@/types.js';
const baseSchema = { const baseSchema = {
@ -312,9 +311,7 @@ export const packedNotificationSchema = {
enum: ['achievementEarned'], enum: ['achievementEarned'],
}, },
achievement: { achievement: {
type: 'string', ref: 'AchievementName',
optional: false, nullable: false,
enum: ACHIEVEMENT_TYPES,
}, },
}, },
}, { }, {

View File

@ -630,18 +630,7 @@ export const packedMeDetailedOnlySchema = {
type: 'array', type: 'array',
nullable: false, optional: false, nullable: false, optional: false,
items: { items: {
type: 'object', ref: 'Achievement',
nullable: false, optional: false,
properties: {
name: {
type: 'string',
nullable: false, optional: false,
},
unlockedAt: {
type: 'number',
nullable: false, optional: false,
},
},
}, },
}, },
loggedInDays: { loggedInDays: {

View File

@ -5,7 +5,8 @@
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { Endpoint } from '@/server/api/endpoint-base.js'; import { Endpoint } from '@/server/api/endpoint-base.js';
import { AchievementService, ACHIEVEMENT_TYPES } from '@/core/AchievementService.js'; import { AchievementService } from '@/core/AchievementService.js';
import { ACHIEVEMENT_TYPES } from '@/models/UserProfile.js';
export const meta = { export const meta = {
requireCredential: true, requireCredential: true,

View File

@ -14,15 +14,7 @@ export const meta = {
res: { res: {
type: 'array', type: 'array',
items: { items: {
type: 'object', ref: 'Achievement',
properties: {
name: {
type: 'string',
},
unlockedAt: {
type: 'number',
},
},
}, },
}, },
} as const; } as const;

View File

@ -232,7 +232,7 @@ describe('UserEntityService', () => {
}); });
test('MeDetailed', async() => { test('MeDetailed', async() => {
const achievements = [{ name: 'achievement', unlockedAt: new Date().getTime() }]; const achievements = [{ name: 'iLoveMisskey' as const, unlockedAt: new Date().getTime() }];
const me = await createUser({}, { const me = await createUser({}, {
birthday: '2000-01-01', birthday: '2000-01-01',
achievements: achievements, achievements: achievements,

View File

@ -30,6 +30,12 @@ declare namespace acct {
} }
export { acct } export { acct }
// @public (undocumented)
type Achievement = components['schemas']['Achievement'];
// @public (undocumented)
type AchievementName = components['schemas']['AchievementName'];
// @public (undocumented) // @public (undocumented)
type Ad = components['schemas']['Ad']; type Ad = components['schemas']['Ad'];
@ -2084,6 +2090,8 @@ declare namespace entities {
UserDetailed, UserDetailed,
User, User,
UserList, UserList,
Achievement,
AchievementName,
Ad, Ad,
Announcement, Announcement,
App, App,

View File

@ -8,6 +8,8 @@ export type MeDetailed = components['schemas']['MeDetailed'];
export type UserDetailed = components['schemas']['UserDetailed']; export type UserDetailed = components['schemas']['UserDetailed'];
export type User = components['schemas']['User']; export type User = components['schemas']['User'];
export type UserList = components['schemas']['UserList']; export type UserList = components['schemas']['UserList'];
export type Achievement = components['schemas']['Achievement'];
export type AchievementName = components['schemas']['AchievementName'];
export type Ad = components['schemas']['Ad']; export type Ad = components['schemas']['Ad'];
export type Announcement = components['schemas']['Announcement']; export type Announcement = components['schemas']['Announcement'];
export type App = components['schemas']['App']; export type App = components['schemas']['App'];

View File

@ -4303,10 +4303,7 @@ export type components = {
}]>; }]>;
}; };
emailNotificationTypes: string[]; emailNotificationTypes: string[];
achievements: { achievements: components['schemas']['Achievement'][];
name: string;
unlockedAt: number;
}[];
loggedInDays: number; loggedInDays: number;
policies: components['schemas']['RolePolicies']; policies: components['schemas']['RolePolicies'];
/** @default false */ /** @default false */
@ -4344,6 +4341,12 @@ export type components = {
userIds?: string[]; userIds?: string[];
isPublic: boolean; isPublic: boolean;
}; };
Achievement: {
name: components['schemas']['AchievementName'];
unlockedAt: number;
};
/** @enum {string} */
AchievementName: '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' | 'loggedInOnNewYearsDay' | 'noteClipped1' | 'noteFavorited1' | 'myNoteFavorited1' | 'profileFilled' | 'markedAsCat' | 'following1' | 'following10' | 'following50' | 'following100' | 'following300' | 'followers1' | 'followers10' | 'followers50' | 'followers100' | 'followers300' | 'followers500' | 'followers1000' | 'collectAchievements30' | 'viewAchievements3min' | 'iLoveMisskey' | 'foundTreasure' | 'client30min' | 'client60min' | 'noteDeletedWithin1min' | 'postedAtLateNight' | 'postedAt0min0sec' | 'selfQuote' | 'htl20npm' | 'viewInstanceChart' | 'outputHelloWorldOnScratchpad' | 'open3windows' | 'driveFolderCircularReference' | 'reactWithoutRead' | 'clickedClickHere' | 'justPlainLucky' | 'setNameToSyuilo' | 'cookieClicked' | 'brainDiver' | 'smashTestNotificationButton' | 'tutorialCompleted' | 'bubbleGameExplodingHead' | 'bubbleGameDoubleExplodingHead';
Ad: { Ad: {
/** /**
* Format: id * Format: id
@ -4619,16 +4622,15 @@ export type components = {
/** @enum {string} */ /** @enum {string} */
type: 'chatRoomInvitationReceived'; type: 'chatRoomInvitationReceived';
invitation: components['schemas']['ChatRoomInvitation']; invitation: components['schemas']['ChatRoomInvitation'];
} | ({ } | {
/** Format: id */ /** Format: id */
id: string; id: string;
/** Format: date-time */ /** Format: date-time */
createdAt: string; createdAt: string;
/** @enum {string} */ /** @enum {string} */
type: 'achievementEarned'; type: 'achievementEarned';
/** @enum {string} */ achievement: components['schemas']['AchievementName'];
achievement: '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' | 'loggedInOnNewYearsDay' | 'noteClipped1' | 'noteFavorited1' | 'myNoteFavorited1' | 'profileFilled' | 'markedAsCat' | 'following1' | 'following10' | 'following50' | 'following100' | 'following300' | 'followers1' | 'followers10' | 'followers50' | 'followers100' | 'followers300' | 'followers500' | 'followers1000' | 'collectAchievements30' | 'viewAchievements3min' | 'iLoveMisskey' | 'foundTreasure' | 'client30min' | 'client60min' | 'noteDeletedWithin1min' | 'postedAtLateNight' | 'postedAt0min0sec' | 'selfQuote' | 'htl20npm' | 'viewInstanceChart' | 'outputHelloWorldOnScratchpad' | 'open3windows' | 'driveFolderCircularReference' | 'reactWithoutRead' | 'clickedClickHere' | 'justPlainLucky' | 'setNameToSyuilo' | 'cookieClicked' | 'brainDiver' | 'smashTestNotificationButton' | 'tutorialCompleted' | 'bubbleGameExplodingHead' | 'bubbleGameDoubleExplodingHead'; } | ({
}) | ({
/** Format: id */ /** Format: id */
id: string; id: string;
/** Format: date-time */ /** Format: date-time */
@ -28531,10 +28533,7 @@ export type operations = {
/** @description OK (with results) */ /** @description OK (with results) */
200: { 200: {
content: { content: {
'application/json': { 'application/json': components['schemas']['Achievement'][];
name: string;
unlockedAt: number;
}[];
}; };
}; };
/** @description Client error */ /** @description Client error */