summaryrefslogtreecommitdiff
path: root/packages/backend/src
diff options
context:
space:
mode:
authorMarie <marie@kaifa.ch>2024-01-11 11:43:20 +0100
committerMarie <marie@kaifa.ch>2024-01-11 11:43:20 +0100
commitbbdc4e77893b359985151f65b342773bc7378b76 (patch)
treee05f1f1d3d0f48cb1208cd7c93ab6107151ac968 /packages/backend/src
parentmerge: upstream (diff)
parentfeat: ranking system of bubble game (diff)
downloadsharkey-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.ts1
-rw-r--r--packages/backend/src/models/BubbleGameRecord.ts57
-rw-r--r--packages/backend/src/models/RepositoryModule.ts10
-rw-r--r--packages/backend/src/models/_.ts3
-rw-r--r--packages/backend/src/postgres.ts2
-rw-r--r--packages/backend/src/server/api/EndpointsModule.ts8
-rw-r--r--packages/backend/src/server/api/endpoints.ts4
-rw-r--r--packages/backend/src/server/api/endpoints/bubble-game/ranking.ts75
-rw-r--r--packages/backend/src/server/api/endpoints/bubble-game/register.ts86
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,
+ });
+ });
+ }
+}