diff options
| author | Marie <marie@kaifa.ch> | 2024-01-11 11:43:20 +0100 |
|---|---|---|
| committer | Marie <marie@kaifa.ch> | 2024-01-11 11:43:20 +0100 |
| commit | bbdc4e77893b359985151f65b342773bc7378b76 (patch) | |
| tree | e05f1f1d3d0f48cb1208cd7c93ab6107151ac968 /packages/backend/src | |
| parent | merge: upstream (diff) | |
| parent | feat: ranking system of bubble game (diff) | |
| download | sharkey-bbdc4e77893b359985151f65b342773bc7378b76.tar.gz sharkey-bbdc4e77893b359985151f65b342773bc7378b76.tar.bz2 sharkey-bbdc4e77893b359985151f65b342773bc7378b76.zip | |
merge: upstream
Diffstat (limited to 'packages/backend/src')
| -rw-r--r-- | packages/backend/src/di-symbols.ts | 1 | ||||
| -rw-r--r-- | packages/backend/src/models/BubbleGameRecord.ts | 57 | ||||
| -rw-r--r-- | packages/backend/src/models/RepositoryModule.ts | 10 | ||||
| -rw-r--r-- | packages/backend/src/models/_.ts | 3 | ||||
| -rw-r--r-- | packages/backend/src/postgres.ts | 2 | ||||
| -rw-r--r-- | packages/backend/src/server/api/EndpointsModule.ts | 8 | ||||
| -rw-r--r-- | packages/backend/src/server/api/endpoints.ts | 4 | ||||
| -rw-r--r-- | packages/backend/src/server/api/endpoints/bubble-game/ranking.ts | 75 | ||||
| -rw-r--r-- | packages/backend/src/server/api/endpoints/bubble-game/register.ts | 86 |
9 files changed, 245 insertions, 1 deletions
diff --git a/packages/backend/src/di-symbols.ts b/packages/backend/src/di-symbols.ts index 0c5ac8f2d3..b6327e5dbc 100644 --- a/packages/backend/src/di-symbols.ts +++ b/packages/backend/src/di-symbols.ts @@ -79,5 +79,6 @@ export const DI = { flashLikesRepository: Symbol('flashLikesRepository'), userMemosRepository: Symbol('userMemosRepository'), noteEditRepository: Symbol('noteEditRepository'), + bubbleGameRecordsRepository: Symbol('bubbleGameRecordsRepository'), //#endregion }; diff --git a/packages/backend/src/models/BubbleGameRecord.ts b/packages/backend/src/models/BubbleGameRecord.ts new file mode 100644 index 0000000000..4b483ed4d3 --- /dev/null +++ b/packages/backend/src/models/BubbleGameRecord.ts @@ -0,0 +1,57 @@ +/* + * SPDX-FileCopyrightText: syuilo and other misskey contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm'; +import { id } from './util/id.js'; +import { MiUser } from './User.js'; + +@Entity('bubble_game_record') +export class MiBubbleGameRecord { + @PrimaryColumn(id()) + public id: string; + + @Index() + @Column({ + ...id(), + }) + public userId: MiUser['id']; + + @ManyToOne(type => MiUser, { + onDelete: 'CASCADE', + }) + @JoinColumn() + public user: MiUser | null; + + @Index() + @Column('timestamp with time zone') + public seededAt: Date; + + @Column('varchar', { + length: 1024, + }) + public seed: string; + + @Column('integer') + public gameVersion: number; + + @Column('varchar', { + length: 128, + }) + public gameMode: string; + + @Index() + @Column('integer') + public score: number; + + @Column('jsonb', { + default: [], + }) + public logs: any[]; + + @Column('boolean', { + default: false, + }) + public isVerified: boolean; +} diff --git a/packages/backend/src/models/RepositoryModule.ts b/packages/backend/src/models/RepositoryModule.ts index 0b5d3b640f..9c42b31743 100644 --- a/packages/backend/src/models/RepositoryModule.ts +++ b/packages/backend/src/models/RepositoryModule.ts @@ -5,7 +5,7 @@ import { Module } from '@nestjs/common'; import { DI } from '@/di-symbols.js'; -import { MiAbuseUserReport, MiAccessToken, MiAd, MiAnnouncement, MiAnnouncementRead, MiAntenna, MiApp, MiAuthSession, MiAvatarDecoration, MiBlocking, MiChannel, MiChannelFavorite, MiChannelFollowing, MiClip, MiClipFavorite, MiClipNote, MiDriveFile, MiDriveFolder, MiEmoji, MiFlash, MiFlashLike, MiFollowRequest, MiFollowing, MiGalleryLike, MiGalleryPost, MiHashtag, MiInstance, MiMeta, MiModerationLog, MiMuting, MiNote, MiNoteFavorite, MiNoteReaction, MiNoteThreadMuting, MiNoteUnread, MiPage, MiPageLike, MiPasswordResetRequest, MiPoll, MiPollVote, MiPromoNote, MiPromoRead, MiRegistrationTicket, MiRegistryItem, MiRelay, MiRenoteMuting, MiRetentionAggregation, MiRole, MiRoleAssignment, MiSignin, MiSwSubscription, MiUsedUsername, MiUser, MiUserIp, MiUserKeypair, MiUserList, MiUserListFavorite, MiUserListMembership, MiUserMemo, MiUserNotePining, MiUserPending, MiUserProfile, MiUserPublickey, MiUserSecurityKey, MiWebhook, NoteEdit } from './_.js'; +import { MiAbuseUserReport, MiAccessToken, MiAd, MiAnnouncement, MiAnnouncementRead, MiAntenna, MiApp, MiAuthSession, MiAvatarDecoration, MiBlocking, MiChannel, MiChannelFavorite, MiChannelFollowing, MiClip, MiClipFavorite, MiClipNote, MiDriveFile, MiDriveFolder, MiEmoji, MiFlash, MiFlashLike, MiFollowRequest, MiFollowing, MiGalleryLike, MiGalleryPost, MiHashtag, MiInstance, MiMeta, MiModerationLog, MiMuting, MiNote, MiNoteFavorite, MiNoteReaction, MiNoteThreadMuting, MiNoteUnread, MiPage, MiPageLike, MiPasswordResetRequest, MiPoll, MiPollVote, MiPromoNote, MiPromoRead, MiRegistrationTicket, MiRegistryItem, MiRelay, MiRenoteMuting, MiRetentionAggregation, MiRole, MiRoleAssignment, MiSignin, MiSwSubscription, MiUsedUsername, MiUser, MiUserIp, MiUserKeypair, MiUserList, MiUserListFavorite, MiUserListMembership, MiUserMemo, MiUserNotePining, MiUserPending, MiUserProfile, MiUserPublickey, MiUserSecurityKey, MiWebhook, NoteEdit, MiBubbleGameRecord } from './_.js'; import type { DataSource } from 'typeorm'; import type { Provider } from '@nestjs/common'; @@ -405,6 +405,12 @@ const $noteEditRepository: Provider = { inject: [DI.db], }; +const $bubbleGameRecordsRepository: Provider = { + provide: DI.bubbleGameRecordsRepository, + useFactory: (db: DataSource) => db.getRepository(MiBubbleGameRecord), + inject: [DI.db], +}; + @Module({ imports: [ ], @@ -475,6 +481,7 @@ const $noteEditRepository: Provider = { $flashLikesRepository, $userMemosRepository, $noteEditRepository, + $bubbleGameRecordsRepository, ], exports: [ $usersRepository, @@ -543,6 +550,7 @@ const $noteEditRepository: Provider = { $flashLikesRepository, $userMemosRepository, $noteEditRepository, + $bubbleGameRecordsRepository, ], }) export class RepositoryModule {} diff --git a/packages/backend/src/models/_.ts b/packages/backend/src/models/_.ts index 2a7810235e..53b9f44cc0 100644 --- a/packages/backend/src/models/_.ts +++ b/packages/backend/src/models/_.ts @@ -69,6 +69,7 @@ import { MiFlash } from '@/models/Flash.js'; import { MiFlashLike } from '@/models/FlashLike.js'; import { MiUserListFavorite } from '@/models/UserListFavorite.js'; import { NoteEdit } from '@/models/NoteEdit.js'; +import { MiBubbleGameRecord } from '@/models/BubbleGameRecord.js'; import type { Repository } from 'typeorm'; export { @@ -138,6 +139,7 @@ export { MiFlashLike, MiUserMemo, NoteEdit, + MiBubbleGameRecord, }; export type AbuseUserReportsRepository = Repository<MiAbuseUserReport>; @@ -206,3 +208,4 @@ export type FlashsRepository = Repository<MiFlash>; export type FlashLikesRepository = Repository<MiFlashLike>; export type UserMemoRepository = Repository<MiUserMemo>; export type NoteEditRepository = Repository<NoteEdit>; +export type BubbleGameRecordsRepository = Repository<MiBubbleGameRecord>; diff --git a/packages/backend/src/postgres.ts b/packages/backend/src/postgres.ts index 18773a1b66..395c7ab44b 100644 --- a/packages/backend/src/postgres.ts +++ b/packages/backend/src/postgres.ts @@ -77,6 +77,7 @@ import { MiFlash } from '@/models/Flash.js'; import { MiFlashLike } from '@/models/FlashLike.js'; import { MiUserMemo } from '@/models/UserMemo.js'; import { NoteEdit } from '@/models/NoteEdit.js'; +import { MiBubbleGameRecord } from '@/models/BubbleGameRecord.js'; import { Config } from '@/config.js'; import MisskeyLogger from '@/logger.js'; @@ -192,6 +193,7 @@ export const entities = [ MiFlashLike, MiUserMemo, NoteEdit, + MiBubbleGameRecord, ...charts, ]; diff --git a/packages/backend/src/server/api/EndpointsModule.ts b/packages/backend/src/server/api/EndpointsModule.ts index f77c50012d..1f37c74c01 100644 --- a/packages/backend/src/server/api/EndpointsModule.ts +++ b/packages/backend/src/server/api/EndpointsModule.ts @@ -377,6 +377,8 @@ import * as ep___fetchRss from './endpoints/fetch-rss.js'; import * as ep___fetchExternalResources from './endpoints/fetch-external-resources.js'; import * as ep___retention from './endpoints/retention.js'; import * as ep___sponsors from './endpoints/sponsors.js'; +import * as ep___bubbleGame_register from './endpoints/bubble-game/register.js'; +import * as ep___bubbleGame_ranking from './endpoints/bubble-game/ranking.js'; import { GetterService } from './GetterService.js'; import { ApiLoggerService } from './ApiLoggerService.js'; import type { Provider } from '@nestjs/common'; @@ -752,6 +754,8 @@ const $fetchRss: Provider = { provide: 'ep:fetch-rss', useClass: ep___fetchRss.d const $fetchExternalResources: Provider = { provide: 'ep:fetch-external-resources', useClass: ep___fetchExternalResources.default }; const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention.default }; const $sponsors: Provider = { provide: 'ep:sponsors', useClass: ep___sponsors.default }; +const $bubbleGame_register: Provider = { provide: 'ep:bubble-game/register', useClass: ep___bubbleGame_register.default }; +const $bubbleGame_ranking: Provider = { provide: 'ep:bubble-game/ranking', useClass: ep___bubbleGame_ranking.default }; @Module({ imports: [ @@ -1131,6 +1135,8 @@ const $sponsors: Provider = { provide: 'ep:sponsors', useClass: ep___sponsors.de $fetchExternalResources, $retention, $sponsors, + $bubbleGame_register, + $bubbleGame_ranking, ], exports: [ $admin_meta, @@ -1501,6 +1507,8 @@ const $sponsors: Provider = { provide: 'ep:sponsors', useClass: ep___sponsors.de $fetchExternalResources, $retention, $sponsors, + $bubbleGame_register, + $bubbleGame_ranking, ], }) export class EndpointsModule {} diff --git a/packages/backend/src/server/api/endpoints.ts b/packages/backend/src/server/api/endpoints.ts index a7be81eb18..7510228bb9 100644 --- a/packages/backend/src/server/api/endpoints.ts +++ b/packages/backend/src/server/api/endpoints.ts @@ -378,6 +378,8 @@ import * as ep___fetchRss from './endpoints/fetch-rss.js'; import * as ep___fetchExternalResources from './endpoints/fetch-external-resources.js'; import * as ep___retention from './endpoints/retention.js'; import * as ep___sponsors from './endpoints/sponsors.js'; +import * as ep___bubbleGame_register from './endpoints/bubble-game/register.js'; +import * as ep___bubbleGame_ranking from './endpoints/bubble-game/ranking.js'; const eps = [ ['admin/meta', ep___admin_meta], @@ -751,6 +753,8 @@ const eps = [ ['fetch-external-resources', ep___fetchExternalResources], ['retention', ep___retention], ['sponsors', ep___sponsors], + ['bubble-game/register', ep___bubbleGame_register], + ['bubble-game/ranking', ep___bubbleGame_ranking], ]; interface IEndpointMetaBase { diff --git a/packages/backend/src/server/api/endpoints/bubble-game/ranking.ts b/packages/backend/src/server/api/endpoints/bubble-game/ranking.ts new file mode 100644 index 0000000000..0cba129a09 --- /dev/null +++ b/packages/backend/src/server/api/endpoints/bubble-game/ranking.ts @@ -0,0 +1,75 @@ +/* + * SPDX-FileCopyrightText: syuilo and other misskey contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { Inject, Injectable } from '@nestjs/common'; +import { MoreThan } from 'typeorm'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import type { BubbleGameRecordsRepository } from '@/models/_.js'; +import { DI } from '@/di-symbols.js'; +import { UserEntityService } from '@/core/entities/UserEntityService.js'; + +export const meta = { + tags: [], + + allowGet: true, + cacheSec: 60, + + errors: { + }, + + res: { + type: 'array', + optional: false, nullable: false, + items: { + type: 'object', + optional: false, nullable: false, + properties: { + id: { type: 'string', format: 'misskey:id' }, + score: { type: 'integer' }, + user: { ref: 'UserLite' }, + }, + }, + }, +} as const; + +export const paramDef = { + type: 'object', + properties: { + gameMode: { type: 'string' }, + }, + required: ['gameMode'], +} as const; + +@Injectable() +export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export + constructor( + @Inject(DI.bubbleGameRecordsRepository) + private bubbleGameRecordsRepository: BubbleGameRecordsRepository, + + private userEntityService: UserEntityService, + ) { + super(meta, paramDef, async (ps) => { + const records = await this.bubbleGameRecordsRepository.find({ + where: { + gameMode: ps.gameMode, + seededAt: MoreThan(new Date(Date.now() - 1000 * 60 * 60 * 24 * 7)), + }, + order: { + score: 'DESC', + }, + take: 10, + relations: ['user'], + }); + + const users = await this.userEntityService.packMany(records.map(r => r.user!), null, { detail: false }); + + return records.map(r => ({ + id: r.id, + score: r.score, + user: users.find(u => u.id === r.user!.id), + })); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/bubble-game/register.ts b/packages/backend/src/server/api/endpoints/bubble-game/register.ts new file mode 100644 index 0000000000..af0f69e4ad --- /dev/null +++ b/packages/backend/src/server/api/endpoints/bubble-game/register.ts @@ -0,0 +1,86 @@ +/* + * SPDX-FileCopyrightText: syuilo and other misskey contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { Inject, Injectable } from '@nestjs/common'; +import ms from 'ms'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { IdService } from '@/core/IdService.js'; +import type { BubbleGameRecordsRepository } from '@/models/_.js'; +import { DI } from '@/di-symbols.js'; +import { ApiError } from '../../error.js'; + +export const meta = { + tags: [], + + requireCredential: true, + + kind: 'write:account', + + limit: { + duration: ms('1hour'), + max: 120, + minInterval: ms('30sec'), + }, + + errors: { + invalidSeed: { + message: 'Provided seed is invalid.', + code: 'INVALID_SEED', + id: 'eb627bc7-574b-4a52-a860-3c3eae772b88', + }, + }, + + res: { + }, +} as const; + +export const paramDef = { + type: 'object', + properties: { + score: { type: 'integer', minimum: 0 }, + seed: { type: 'string', minLength: 1, maxLength: 1024 }, + logs: { type: 'array' }, + gameMode: { type: 'string' }, + gameVersion: { type: 'integer' }, + }, + required: ['score', 'seed', 'logs', 'gameMode', 'gameVersion'], +} as const; + +@Injectable() +export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export + constructor( + @Inject(DI.bubbleGameRecordsRepository) + private bubbleGameRecordsRepository: BubbleGameRecordsRepository, + + private idService: IdService, + ) { + super(meta, paramDef, async (ps, me) => { + const seedDate = new Date(parseInt(ps.seed, 10)); + const now = new Date(); + + // シードが未来なのは通常のプレイではありえないので弾く + if (seedDate.getTime() > now.getTime()) { + throw new ApiError(meta.errors.invalidSeed); + } + + // シードが古すぎる(1時間以上前)のも弾く + if (seedDate.getTime() < now.getTime() - 1000 * 60 * 60) { + throw new ApiError(meta.errors.invalidSeed); + } + + await this.bubbleGameRecordsRepository.insert({ + id: this.idService.gen(now.getTime()), + seed: ps.seed, + seededAt: seedDate, + userId: me.id, + score: ps.score, + logs: ps.logs, + gameMode: ps.gameMode, + gameVersion: ps.gameVersion, + isVerified: false, + }); + }); + } +} |