This commit is contained in:
syuilo 2026-05-14 19:08:36 +09:00
commit d38f04c97f
10 changed files with 239 additions and 5 deletions

View file

@ -10,7 +10,7 @@ export class WorldRoom1778744540138 {
* @param {QueryRunner} queryRunner
*/
async up(queryRunner) {
await queryRunner.query(`CREATE TABLE "world_room" ("id" character varying(32) NOT NULL, "updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL, "name" character varying(256) NOT NULL, "description" character varying(1024) NOT NULL, "userId" character varying(32) NOT NULL, "likedCount" integer NOT NULL DEFAULT '0', "visibility" character varying(128) NOT NULL DEFAULT 'public', "def" jsonb NOT NULL DEFAULT '{}', CONSTRAINT "PK_40cfacaf35b0b54bb2281c89767" PRIMARY KEY ("id")); COMMENT ON COLUMN "world_room"."userId" IS 'The ID of author.'`);
await queryRunner.query(`CREATE TABLE "world_room" ("id" character varying(32) NOT NULL, "updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL, "name" character varying(256) NOT NULL, "description" character varying(1024) NOT NULL, "userId" character varying(32) NOT NULL, "likedCount" integer NOT NULL DEFAULT '0', "visibility" character varying(128) NOT NULL DEFAULT 'public', "def" jsonb NOT NULL DEFAULT '{}', CONSTRAINT "PK_40cfacaf35b0b54bb2281c89767" PRIMARY KEY ("id"))`);
await queryRunner.query(`CREATE INDEX "IDX_88289375952050da4a7752a366" ON "world_room" ("updatedAt") `);
await queryRunner.query(`CREATE INDEX "IDX_f803a5efb4125c5fd8a414285e" ON "world_room" ("userId") `);
await queryRunner.query(`ALTER TABLE "world_room" ADD CONSTRAINT "FK_f803a5efb4125c5fd8a414285ed" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);

View file

@ -0,0 +1,97 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { Inject, Injectable } from '@nestjs/common';
import { DI } from '@/di-symbols.js';
import type { DriveFilesRepository, MiWorldRoom, WorldRoomsRepository } from '@/models/_.js';
import { awaitAll } from '@/misc/prelude/await-all.js';
import type { Packed } from '@/misc/json-schema.js';
import type { } from '@/models/Blocking.js';
import type { MiUser } from '@/models/User.js';
import type { MiDriveFile } from '@/models/DriveFile.js';
import { bindThis } from '@/decorators.js';
import { IdService } from '@/core/IdService.js';
import { WorldRoomService } from '@/core/WorldRoomService.js';
import { UserEntityService } from './UserEntityService.js';
import { DriveFileEntityService } from './DriveFileEntityService.js';
import { In } from 'typeorm';
@Injectable()
export class WorldRoomEntityService {
constructor(
@Inject(DI.worldRoomsRepository)
private worldRoomsRepository: WorldRoomsRepository,
@Inject(DI.driveFilesRepository)
private driveFilesRepository: DriveFilesRepository,
private worldRoomService: WorldRoomService,
private userEntityService: UserEntityService,
private driveFileEntityService: DriveFileEntityService,
private idService: IdService,
) {
}
@bindThis
public async packLite(
src: MiWorldRoom['id'] | MiWorldRoom,
me?: { id: MiUser['id'] } | null | undefined,
hint?: {
packedUser?: Packed<'UserLite'>
},
): Promise<Packed<'WorldRoomLite'>> {
const meId = me ? me.id : null;
const room = typeof src === 'object' ? src : await this.worldRoomsRepository.findOneByOrFail({ id: src });
return await awaitAll({
id: room.id,
createdAt: this.idService.parse(room.id).date.toISOString(),
updatedAt: room.updatedAt.toISOString(),
userId: room.userId,
user: hint?.packedUser ?? this.userEntityService.pack(room.user ?? room.userId, me),
name: room.name,
description: room.description,
});
}
@bindThis
public async packDetailed(
src: MiWorldRoom['id'] | MiWorldRoom,
me?: { id: MiUser['id'] } | null | undefined,
hint?: {
packedUser?: Packed<'UserLite'>
},
): Promise<Packed<'WorldRoomDetailed'>> {
const meId = me ? me.id : null;
const room = typeof src === 'object' ? src : await this.worldRoomsRepository.findOneByOrFail({ id: src });
const attachedFileIds = this.worldRoomService.collectReferencedDriveFileIds(room.def);
const attachedFiles = attachedFileIds.size === 0 ? [] : await this.driveFilesRepository.findBy({ id: In([...attachedFileIds]), userId: room.userId });
return await awaitAll({
id: room.id,
createdAt: this.idService.parse(room.id).date.toISOString(),
updatedAt: room.updatedAt.toISOString(),
userId: room.userId,
user: hint?.packedUser ?? this.userEntityService.pack(room.user ?? room.userId, me),
name: room.name,
description: room.description,
def: room.def,
attachedFiles: this.driveFileEntityService.packMany(attachedFiles),
});
}
@bindThis
public async packLiteMany(
rooms: MiWorldRoom[],
me?: { id: MiUser['id'] } | null | undefined,
) {
const _users = rooms.map(({ user, userId }) => user ?? userId);
const _userMap = await this.userEntityService.packMany(_users, me)
.then(users => new Map(users.map(u => [u.id, u])));
return Promise.all(rooms.map(room => this.packLite(room, me, { packedUser: _userMap.get(room.userId) })));
}
}

View file

@ -75,6 +75,7 @@ import { packedChatRoomInvitationSchema } from '@/models/json-schema/chat-room-i
import { packedChatRoomMembershipSchema } from '@/models/json-schema/chat-room-membership.js';
import { packedAchievementNameSchema, packedAchievementSchema } from '@/models/json-schema/achievement.js';
import { packedNoteDraftSchema } from '@/models/json-schema/note-draft.js';
import { packedWorldRoomDetailedSchema, packedWorldRoomLiteSchema } from '@/models/json-schema/world-room.js';
export const refs = {
UserLite: packedUserLiteSchema,
@ -147,6 +148,8 @@ export const refs = {
ChatRoom: packedChatRoomSchema,
ChatRoomInvitation: packedChatRoomInvitationSchema,
ChatRoomMembership: packedChatRoomMembershipSchema,
WorldRoomLite: packedWorldRoomLiteSchema,
WorldRoomDetailed: packedWorldRoomDetailedSchema,
};
export type Packed<x extends keyof typeof refs> = SchemaType<typeof refs[x]>;

View file

@ -33,7 +33,6 @@ export class MiWorldRoom {
@Index()
@Column({
...id(),
comment: 'The ID of author.',
})
public userId: MiUser['id'];

View file

@ -0,0 +1,95 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
export const packedWorldRoomLiteSchema = {
type: 'object',
properties: {
id: {
type: 'string',
optional: false, nullable: false,
format: 'id',
},
createdAt: {
type: 'string',
optional: false, nullable: false,
format: 'date-time',
},
updatedAt: {
type: 'string',
optional: false, nullable: false,
format: 'date-time',
},
userId: {
type: 'string',
optional: false, nullable: false,
format: 'id',
},
user: {
type: 'object',
ref: 'UserLite',
optional: false, nullable: false,
},
name: {
type: 'string',
optional: false, nullable: false,
},
description: {
type: 'string',
optional: false, nullable: false,
},
},
} as const;
export const packedWorldRoomDetailedSchema = {
type: 'object',
properties: {
id: {
type: 'string',
optional: false, nullable: false,
format: 'id',
},
createdAt: {
type: 'string',
optional: false, nullable: false,
format: 'date-time',
},
updatedAt: {
type: 'string',
optional: false, nullable: false,
format: 'date-time',
},
userId: {
type: 'string',
optional: false, nullable: false,
format: 'id',
},
user: {
type: 'object',
ref: 'UserLite',
optional: false, nullable: false,
},
name: {
type: 'string',
optional: false, nullable: false,
},
description: {
type: 'string',
optional: false, nullable: false,
},
def: {
type: 'object',
optional: false, nullable: false,
},
attachedFiles: {
type: 'array',
optional: false, nullable: false,
items: {
type: 'object',
optional: false, nullable: false,
ref: 'DriveFile',
},
},
},
} as const;

View file

@ -43,7 +43,7 @@ import * as os from '@/os.js';
import MkInput from '@/components/MkInput.vue';
import MkSwitch from '@/components/MkSwitch.vue';
import MkRange from '@/components/MkRange.vue';
import { WorldController } from '@/world/controller.js';
import { WorldEngineController } from '@/world/controller.js';
const canvas = useTemplateRef('canvas');
@ -60,7 +60,7 @@ function resize() {
const isZenMode = ref(false);
const controller = new WorldController();
const controller = new WorldEngineController();
onMounted(async () => {
controller.init(canvas.value!);

View file

@ -22,6 +22,7 @@ export class WorldEngineController extends EngineControllerBase {
}
public async init(canvas: HTMLCanvasElement) {
/*
await this._init_(canvas, {
createWorker: (offscreen) => new Promise((resolve) => {
import('./worker?worker').then(({ default: WorldEngineWorker }) => {
@ -39,5 +40,6 @@ export class WorldEngineController extends EngineControllerBase {
});
}),
});
*/
}
}

View file

@ -2252,7 +2252,9 @@ declare namespace entities {
ChatMessageLiteForRoom,
ChatRoom,
ChatRoomInvitation,
ChatRoomMembership
ChatRoomMembership,
WorldRoomLite,
WorldRoomDetailed
}
}
export { entities }
@ -3901,6 +3903,12 @@ type V2AdminEmojiListResponse = operations['v2___admin___emoji___list']['respons
// @public (undocumented)
type VerifyEmailRequest = operations['verify-email']['requestBody']['content']['application/json'];
// @public (undocumented)
type WorldRoomDetailed = components['schemas']['WorldRoomDetailed'];
// @public (undocumented)
type WorldRoomLite = components['schemas']['WorldRoomLite'];
// Warnings were encountered during analysis:
//
// src/entities.ts:60:2 - (ae-forgotten-export) The symbol "ModerationLogPayloads" needs to be exported by the entry point index.d.ts

View file

@ -69,3 +69,5 @@ export type ChatMessageLiteForRoom = components['schemas']['ChatMessageLiteForRo
export type ChatRoom = components['schemas']['ChatRoom'];
export type ChatRoomInvitation = components['schemas']['ChatRoomInvitation'];
export type ChatRoomMembership = components['schemas']['ChatRoomMembership'];
export type WorldRoomLite = components['schemas']['WorldRoomLite'];
export type WorldRoomDetailed = components['schemas']['WorldRoomDetailed'];

View file

@ -5688,6 +5688,34 @@ export type components = {
roomId: string;
room?: components['schemas']['ChatRoom'];
};
WorldRoomLite: {
/** Format: id */
id: string;
/** Format: date-time */
createdAt: string;
/** Format: date-time */
updatedAt: string;
/** Format: id */
userId: string;
user: components['schemas']['UserLite'];
name: string;
description: string;
};
WorldRoomDetailed: {
/** Format: id */
id: string;
/** Format: date-time */
createdAt: string;
/** Format: date-time */
updatedAt: string;
/** Format: id */
userId: string;
user: components['schemas']['UserLite'];
name: string;
description: string;
def: Record<string, never>;
attachedFiles: components['schemas']['DriveFile'][];
};
};
responses: never;
parameters: never;