summaryrefslogtreecommitdiff
path: root/packages/backend/src
diff options
context:
space:
mode:
authorsyuilo <Syuilotan@yahoo.co.jp>2023-01-05 13:59:48 +0900
committerGitHub <noreply@github.com>2023-01-05 13:59:48 +0900
commitebe340d5105595abe2406e8f386c3ab69703b73b (patch)
tree36eb93333667353fb71a430b7d5e1a700d0e904e /packages/backend/src
parentUpdate CHANGELOG.md (diff)
downloadmisskey-ebe340d5105595abe2406e8f386c3ab69703b73b.tar.gz
misskey-ebe340d5105595abe2406e8f386c3ab69703b73b.tar.bz2
misskey-ebe340d5105595abe2406e8f386c3ab69703b73b.zip
MisskeyPlay (#9467)
* wip * wip * wip * wip * wip * Update ui.ts * wip * wip * wip * wip * wip * wip * wip * wip * Update CHANGELOG.md * wip * wip * wip * wip * :art: * wip * :v:
Diffstat (limited to 'packages/backend/src')
-rw-r--r--packages/backend/src/core/CoreModule.ts12
-rw-r--r--packages/backend/src/core/entities/FlashEntityService.ts55
-rw-r--r--packages/backend/src/core/entities/FlashLikeEntityService.ts44
-rw-r--r--packages/backend/src/di-symbols.ts2
-rw-r--r--packages/backend/src/models/RepositoryModule.ts18
-rw-r--r--packages/backend/src/models/entities/Flash.ts60
-rw-r--r--packages/backend/src/models/entities/FlashLike.ts33
-rw-r--r--packages/backend/src/models/index.ts6
-rw-r--r--packages/backend/src/postgre.ts4
-rw-r--r--packages/backend/src/server/api/EndpointsModule.ts36
-rw-r--r--packages/backend/src/server/api/endpoints.ts18
-rw-r--r--packages/backend/src/server/api/endpoints/flash/create.ts66
-rw-r--r--packages/backend/src/server/api/endpoints/flash/delete.ts56
-rw-r--r--packages/backend/src/server/api/endpoints/flash/featured.ts48
-rw-r--r--packages/backend/src/server/api/endpoints/flash/like.ts87
-rw-r--r--packages/backend/src/server/api/endpoints/flash/my-likes.ts68
-rw-r--r--packages/backend/src/server/api/endpoints/flash/my.ts57
-rw-r--r--packages/backend/src/server/api/endpoints/flash/show.ts60
-rw-r--r--packages/backend/src/server/api/endpoints/flash/unlike.ts68
-rw-r--r--packages/backend/src/server/api/endpoints/flash/update.ts78
20 files changed, 875 insertions, 1 deletions
diff --git a/packages/backend/src/core/CoreModule.ts b/packages/backend/src/core/CoreModule.ts
index 7c6d12abf8..2f17fa389a 100644
--- a/packages/backend/src/core/CoreModule.ts
+++ b/packages/backend/src/core/CoreModule.ts
@@ -95,6 +95,8 @@ import { UserEntityService } from './entities/UserEntityService.js';
import { UserGroupEntityService } from './entities/UserGroupEntityService.js';
import { UserGroupInvitationEntityService } from './entities/UserGroupInvitationEntityService.js';
import { UserListEntityService } from './entities/UserListEntityService.js';
+import { FlashEntityService } from './entities/FlashEntityService.js';
+import { FlashLikeEntityService } from './entities/FlashLikeEntityService.js';
import { ApAudienceService } from './activitypub/ApAudienceService.js';
import { ApDbResolverService } from './activitypub/ApDbResolverService.js';
import { ApDeliverManagerService } from './activitypub/ApDeliverManagerService.js';
@@ -216,6 +218,8 @@ const $UserEntityService: Provider = { provide: 'UserEntityService', useExisting
const $UserGroupEntityService: Provider = { provide: 'UserGroupEntityService', useExisting: UserGroupEntityService };
const $UserGroupInvitationEntityService: Provider = { provide: 'UserGroupInvitationEntityService', useExisting: UserGroupInvitationEntityService };
const $UserListEntityService: Provider = { provide: 'UserListEntityService', useExisting: UserListEntityService };
+const $FlashEntityService: Provider = { provide: 'FlashEntityService', useExisting: FlashEntityService };
+const $FlashLikeEntityService: Provider = { provide: 'FlashLikeEntityService', useExisting: FlashLikeEntityService };
const $ApAudienceService: Provider = { provide: 'ApAudienceService', useExisting: ApAudienceService };
const $ApDbResolverService: Provider = { provide: 'ApDbResolverService', useExisting: ApDbResolverService };
@@ -338,6 +342,8 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
UserGroupEntityService,
UserGroupInvitationEntityService,
UserListEntityService,
+ FlashEntityService,
+ FlashLikeEntityService,
ApAudienceService,
ApDbResolverService,
ApDeliverManagerService,
@@ -455,6 +461,8 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
$UserGroupEntityService,
$UserGroupInvitationEntityService,
$UserListEntityService,
+ $FlashEntityService,
+ $FlashLikeEntityService,
$ApAudienceService,
$ApDbResolverService,
$ApDeliverManagerService,
@@ -572,6 +580,8 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
UserGroupEntityService,
UserGroupInvitationEntityService,
UserListEntityService,
+ FlashEntityService,
+ FlashLikeEntityService,
ApAudienceService,
ApDbResolverService,
ApDeliverManagerService,
@@ -688,6 +698,8 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
$UserGroupEntityService,
$UserGroupInvitationEntityService,
$UserListEntityService,
+ $FlashEntityService,
+ $FlashLikeEntityService,
$ApAudienceService,
$ApDbResolverService,
$ApDeliverManagerService,
diff --git a/packages/backend/src/core/entities/FlashEntityService.ts b/packages/backend/src/core/entities/FlashEntityService.ts
new file mode 100644
index 0000000000..61bd18c04f
--- /dev/null
+++ b/packages/backend/src/core/entities/FlashEntityService.ts
@@ -0,0 +1,55 @@
+import { Inject, Injectable } from '@nestjs/common';
+import { DI } from '@/di-symbols.js';
+import type { FlashsRepository, FlashLikesRepository } from '@/models/index.js';
+import { awaitAll } from '@/misc/prelude/await-all.js';
+import type { Packed } from '@/misc/schema.js';
+import type { } from '@/models/entities/Blocking.js';
+import type { User } from '@/models/entities/User.js';
+import type { Flash } from '@/models/entities/Flash.js';
+import { bindThis } from '@/decorators.js';
+import { UserEntityService } from './UserEntityService.js';
+
+@Injectable()
+export class FlashEntityService {
+ constructor(
+ @Inject(DI.flashsRepository)
+ private flashsRepository: FlashsRepository,
+
+ @Inject(DI.flashLikesRepository)
+ private flashLikesRepository: FlashLikesRepository,
+
+ private userEntityService: UserEntityService,
+ ) {
+ }
+
+ @bindThis
+ public async pack(
+ src: Flash['id'] | Flash,
+ me?: { id: User['id'] } | null | undefined,
+ ): Promise<Packed<'Flash'>> {
+ const meId = me ? me.id : null;
+ const flash = typeof src === 'object' ? src : await this.flashsRepository.findOneByOrFail({ id: src });
+
+ return await awaitAll({
+ id: flash.id,
+ createdAt: flash.createdAt.toISOString(),
+ updatedAt: flash.updatedAt.toISOString(),
+ userId: flash.userId,
+ user: this.userEntityService.pack(flash.user ?? flash.userId, me), // { detail: true } すると無限ループするので注意
+ title: flash.title,
+ summary: flash.summary,
+ script: flash.script,
+ likedCount: flash.likedCount,
+ isLiked: meId ? await this.flashLikesRepository.findOneBy({ flashId: flash.id, userId: meId }).then(x => x != null) : undefined,
+ });
+ }
+
+ @bindThis
+ public packMany(
+ flashs: Flash[],
+ me?: { id: User['id'] } | null | undefined,
+ ) {
+ return Promise.all(flashs.map(x => this.pack(x, me)));
+ }
+}
+
diff --git a/packages/backend/src/core/entities/FlashLikeEntityService.ts b/packages/backend/src/core/entities/FlashLikeEntityService.ts
new file mode 100644
index 0000000000..dcf12d53ea
--- /dev/null
+++ b/packages/backend/src/core/entities/FlashLikeEntityService.ts
@@ -0,0 +1,44 @@
+import { Inject, Injectable } from '@nestjs/common';
+import { DI } from '@/di-symbols.js';
+import type { FlashLikesRepository } from '@/models/index.js';
+import { awaitAll } from '@/misc/prelude/await-all.js';
+import type { Packed } from '@/misc/schema.js';
+import type { } from '@/models/entities/Blocking.js';
+import type { User } from '@/models/entities/User.js';
+import type { FlashLike } from '@/models/entities/FlashLike.js';
+import { bindThis } from '@/decorators.js';
+import { UserEntityService } from './UserEntityService.js';
+import { FlashEntityService } from './FlashEntityService.js';
+
+@Injectable()
+export class FlashLikeEntityService {
+ constructor(
+ @Inject(DI.flashLikesRepository)
+ private flashLikesRepository: FlashLikesRepository,
+
+ private flashEntityService: FlashEntityService,
+ ) {
+ }
+
+ @bindThis
+ public async pack(
+ src: FlashLike['id'] | FlashLike,
+ me?: { id: User['id'] } | null | undefined,
+ ) {
+ const like = typeof src === 'object' ? src : await this.flashLikesRepository.findOneByOrFail({ id: src });
+
+ return {
+ id: like.id,
+ flash: await this.flashEntityService.pack(like.flash ?? like.flashId, me),
+ };
+ }
+
+ @bindThis
+ public packMany(
+ likes: any[],
+ me: { id: User['id'] },
+ ) {
+ return Promise.all(likes.map(x => this.pack(x, me)));
+ }
+}
+
diff --git a/packages/backend/src/di-symbols.ts b/packages/backend/src/di-symbols.ts
index d2a361405f..9719d773ca 100644
--- a/packages/backend/src/di-symbols.ts
+++ b/packages/backend/src/di-symbols.ts
@@ -69,5 +69,7 @@ export const DI = {
adsRepository: Symbol('adsRepository'),
passwordResetRequestsRepository: Symbol('passwordResetRequestsRepository'),
retentionAggregationsRepository: Symbol('retentionAggregationsRepository'),
+ flashsRepository: Symbol('flashsRepository'),
+ flashLikesRepository: Symbol('flashLikesRepository'),
//#endregion
};
diff --git a/packages/backend/src/models/RepositoryModule.ts b/packages/backend/src/models/RepositoryModule.ts
index e22f0517ca..a5d5a63931 100644
--- a/packages/backend/src/models/RepositoryModule.ts
+++ b/packages/backend/src/models/RepositoryModule.ts
@@ -1,6 +1,6 @@
import { Module } from '@nestjs/common';
import { DI } from '@/di-symbols.js';
-import { User, Note, Announcement, AnnouncementRead, App, NoteFavorite, NoteThreadMuting, NoteReaction, NoteUnread, Notification, Poll, PollVote, UserProfile, UserKeypair, UserPending, AttestationChallenge, UserSecurityKey, UserPublickey, UserList, UserListJoining, UserGroup, UserGroupJoining, UserGroupInvitation, UserNotePining, UserIp, UsedUsername, Following, FollowRequest, Instance, Emoji, DriveFile, DriveFolder, Meta, Muting, Blocking, SwSubscription, Hashtag, AbuseUserReport, RegistrationTicket, AuthSession, AccessToken, Signin, MessagingMessage, Page, PageLike, GalleryPost, GalleryLike, ModerationLog, Clip, ClipNote, Antenna, AntennaNote, PromoNote, PromoRead, Relay, MutedNote, Channel, ChannelFollowing, ChannelNotePining, RegistryItem, Webhook, Ad, PasswordResetRequest, RetentionAggregation } from './index.js';
+import { User, Note, Announcement, AnnouncementRead, App, NoteFavorite, NoteThreadMuting, NoteReaction, NoteUnread, Notification, Poll, PollVote, UserProfile, UserKeypair, UserPending, AttestationChallenge, UserSecurityKey, UserPublickey, UserList, UserListJoining, UserGroup, UserGroupJoining, UserGroupInvitation, UserNotePining, UserIp, UsedUsername, Following, FollowRequest, Instance, Emoji, DriveFile, DriveFolder, Meta, Muting, Blocking, SwSubscription, Hashtag, AbuseUserReport, RegistrationTicket, AuthSession, AccessToken, Signin, MessagingMessage, Page, PageLike, GalleryPost, GalleryLike, ModerationLog, Clip, ClipNote, Antenna, AntennaNote, PromoNote, PromoRead, Relay, MutedNote, Channel, ChannelFollowing, ChannelNotePining, RegistryItem, Webhook, Ad, PasswordResetRequest, RetentionAggregation, FlashLike, Flash } from './index.js';
import type { DataSource } from 'typeorm';
import type { Provider } from '@nestjs/common';
@@ -388,6 +388,18 @@ const $retentionAggregationsRepository: Provider = {
inject: [DI.db],
};
+const $flashsRepository: Provider = {
+ provide: DI.flashsRepository,
+ useFactory: (db: DataSource) => db.getRepository(Flash),
+ inject: [DI.db],
+};
+
+const $flashLikesRepository: Provider = {
+ provide: DI.flashLikesRepository,
+ useFactory: (db: DataSource) => db.getRepository(FlashLike),
+ inject: [DI.db],
+};
+
@Module({
imports: [
],
@@ -456,6 +468,8 @@ const $retentionAggregationsRepository: Provider = {
$adsRepository,
$passwordResetRequestsRepository,
$retentionAggregationsRepository,
+ $flashsRepository,
+ $flashLikesRepository,
],
exports: [
$usersRepository,
@@ -522,6 +536,8 @@ const $retentionAggregationsRepository: Provider = {
$adsRepository,
$passwordResetRequestsRepository,
$retentionAggregationsRepository,
+ $flashsRepository,
+ $flashLikesRepository,
],
})
export class RepositoryModule {}
diff --git a/packages/backend/src/models/entities/Flash.ts b/packages/backend/src/models/entities/Flash.ts
new file mode 100644
index 0000000000..d9a6ac987c
--- /dev/null
+++ b/packages/backend/src/models/entities/Flash.ts
@@ -0,0 +1,60 @@
+import { Entity, Index, JoinColumn, Column, PrimaryColumn, ManyToOne } from 'typeorm';
+import { id } from '../id.js';
+import { User } from './User.js';
+import { DriveFile } from './DriveFile.js';
+
+@Entity()
+export class Flash {
+ @PrimaryColumn(id())
+ public id: string;
+
+ @Index()
+ @Column('timestamp with time zone', {
+ comment: 'The created date of the Flash.',
+ })
+ public createdAt: Date;
+
+ @Index()
+ @Column('timestamp with time zone', {
+ comment: 'The updated date of the Flash.',
+ })
+ public updatedAt: Date;
+
+ @Column('varchar', {
+ length: 256,
+ })
+ public title: string;
+
+ @Column('varchar', {
+ length: 1024,
+ })
+ public summary: string;
+
+ @Index()
+ @Column({
+ ...id(),
+ comment: 'The ID of author.',
+ })
+ public userId: User['id'];
+
+ @ManyToOne(type => User, {
+ onDelete: 'CASCADE',
+ })
+ @JoinColumn()
+ public user: User | null;
+
+ @Column('varchar', {
+ length: 16384,
+ })
+ public script: string;
+
+ @Column('varchar', {
+ length: 256, array: true, default: '{}',
+ })
+ public permissions: string[];
+
+ @Column('integer', {
+ default: 0,
+ })
+ public likedCount: number;
+}
diff --git a/packages/backend/src/models/entities/FlashLike.ts b/packages/backend/src/models/entities/FlashLike.ts
new file mode 100644
index 0000000000..81d39191ca
--- /dev/null
+++ b/packages/backend/src/models/entities/FlashLike.ts
@@ -0,0 +1,33 @@
+import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm';
+import { id } from '../id.js';
+import { User } from './User.js';
+import { Flash } from './Flash.js';
+
+@Entity()
+@Index(['userId', 'flashId'], { unique: true })
+export class FlashLike {
+ @PrimaryColumn(id())
+ public id: string;
+
+ @Column('timestamp with time zone')
+ public createdAt: Date;
+
+ @Index()
+ @Column(id())
+ public userId: User['id'];
+
+ @ManyToOne(type => User, {
+ onDelete: 'CASCADE',
+ })
+ @JoinColumn()
+ public user: User | null;
+
+ @Column(id())
+ public flashId: Flash['id'];
+
+ @ManyToOne(type => Flash, {
+ onDelete: 'CASCADE',
+ })
+ @JoinColumn()
+ public flash: Flash | null;
+}
diff --git a/packages/backend/src/models/index.ts b/packages/backend/src/models/index.ts
index ca7a7c9e56..b132475747 100644
--- a/packages/backend/src/models/index.ts
+++ b/packages/backend/src/models/index.ts
@@ -62,6 +62,8 @@ import { UserSecurityKey } from '@/models/entities/UserSecurityKey.js';
import { Webhook } from '@/models/entities/Webhook.js';
import { Channel } from '@/models/entities/Channel.js';
import { RetentionAggregation } from '@/models/entities/RetentionAggregation.js';
+import { Flash } from '@/models/entities/Flash.js';
+import { FlashLike } from '@/models/entities/FlashLike.js';
import type { Repository } from 'typeorm';
export {
@@ -129,6 +131,8 @@ export {
Webhook,
Channel,
RetentionAggregation,
+ Flash,
+ FlashLike,
};
export type AbuseUserReportsRepository = Repository<AbuseUserReport>;
@@ -195,3 +199,5 @@ export type UserSecurityKeysRepository = Repository<UserSecurityKey>;
export type WebhooksRepository = Repository<Webhook>;
export type ChannelsRepository = Repository<Channel>;
export type RetentionAggregationsRepository = Repository<RetentionAggregation>;
+export type FlashsRepository = Repository<Flash>;
+export type FlashLikesRepository = Repository<FlashLike>;
diff --git a/packages/backend/src/postgre.ts b/packages/backend/src/postgre.ts
index 4b4490a0c3..4f6b157d80 100644
--- a/packages/backend/src/postgre.ts
+++ b/packages/backend/src/postgre.ts
@@ -70,6 +70,8 @@ import { UserSecurityKey } from '@/models/entities/UserSecurityKey.js';
import { Webhook } from '@/models/entities/Webhook.js';
import { Channel } from '@/models/entities/Channel.js';
import { RetentionAggregation } from '@/models/entities/RetentionAggregation.js';
+import { Flash } from '@/models/entities/Flash.js';
+import { FlashLike } from '@/models/entities/FlashLike.js';
import { Config } from '@/config.js';
import MisskeyLogger from '@/logger.js';
@@ -184,6 +186,8 @@ export const entities = [
Webhook,
UserIp,
RetentionAggregation,
+ Flash,
+ FlashLike,
...charts,
];
diff --git a/packages/backend/src/server/api/EndpointsModule.ts b/packages/backend/src/server/api/EndpointsModule.ts
index 32eff7f312..60beca4f47 100644
--- a/packages/backend/src/server/api/EndpointsModule.ts
+++ b/packages/backend/src/server/api/EndpointsModule.ts
@@ -266,6 +266,15 @@ import * as ep___pages_like from './endpoints/pages/like.js';
import * as ep___pages_show from './endpoints/pages/show.js';
import * as ep___pages_unlike from './endpoints/pages/unlike.js';
import * as ep___pages_update from './endpoints/pages/update.js';
+import * as ep___flash_create from './endpoints/flash/create.js';
+import * as ep___flash_delete from './endpoints/flash/delete.js';
+import * as ep___flash_featured from './endpoints/flash/featured.js';
+import * as ep___flash_like from './endpoints/flash/like.js';
+import * as ep___flash_show from './endpoints/flash/show.js';
+import * as ep___flash_unlike from './endpoints/flash/unlike.js';
+import * as ep___flash_update from './endpoints/flash/update.js';
+import * as ep___flash_my from './endpoints/flash/my.js';
+import * as ep___flash_myLikes from './endpoints/flash/my-likes.js';
import * as ep___ping from './endpoints/ping.js';
import * as ep___pinnedUsers from './endpoints/pinned-users.js';
import * as ep___promo_read from './endpoints/promo/read.js';
@@ -587,6 +596,15 @@ const $pages_like: Provider = { provide: 'ep:pages/like', useClass: ep___pages_l
const $pages_show: Provider = { provide: 'ep:pages/show', useClass: ep___pages_show.default };
const $pages_unlike: Provider = { provide: 'ep:pages/unlike', useClass: ep___pages_unlike.default };
const $pages_update: Provider = { provide: 'ep:pages/update', useClass: ep___pages_update.default };
+const $flash_create: Provider = { provide: 'ep:flash/create', useClass: ep___flash_create.default };
+const $flash_delete: Provider = { provide: 'ep:flash/delete', useClass: ep___flash_delete.default };
+const $flash_featured: Provider = { provide: 'ep:flash/featured', useClass: ep___flash_featured.default };
+const $flash_like: Provider = { provide: 'ep:flash/like', useClass: ep___flash_like.default };
+const $flash_show: Provider = { provide: 'ep:flash/show', useClass: ep___flash_show.default };
+const $flash_unlike: Provider = { provide: 'ep:flash/unlike', useClass: ep___flash_unlike.default };
+const $flash_update: Provider = { provide: 'ep:flash/update', useClass: ep___flash_update.default };
+const $flash_my: Provider = { provide: 'ep:flash/my', useClass: ep___flash_my.default };
+const $flash_myLikes: Provider = { provide: 'ep:flash/my-likes', useClass: ep___flash_myLikes.default };
const $ping: Provider = { provide: 'ep:ping', useClass: ep___ping.default };
const $pinnedUsers: Provider = { provide: 'ep:pinned-users', useClass: ep___pinnedUsers.default };
const $promo_read: Provider = { provide: 'ep:promo/read', useClass: ep___promo_read.default };
@@ -912,6 +930,15 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention
$pages_show,
$pages_unlike,
$pages_update,
+ $flash_create,
+ $flash_delete,
+ $flash_featured,
+ $flash_like,
+ $flash_show,
+ $flash_unlike,
+ $flash_update,
+ $flash_my,
+ $flash_myLikes,
$ping,
$pinnedUsers,
$promo_read,
@@ -1231,6 +1258,15 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention
$pages_show,
$pages_unlike,
$pages_update,
+ $flash_create,
+ $flash_delete,
+ $flash_featured,
+ $flash_like,
+ $flash_show,
+ $flash_unlike,
+ $flash_update,
+ $flash_my,
+ $flash_myLikes,
$ping,
$pinnedUsers,
$promo_read,
diff --git a/packages/backend/src/server/api/endpoints.ts b/packages/backend/src/server/api/endpoints.ts
index 49dc3b224f..d4f8be5b85 100644
--- a/packages/backend/src/server/api/endpoints.ts
+++ b/packages/backend/src/server/api/endpoints.ts
@@ -265,6 +265,15 @@ import * as ep___pages_like from './endpoints/pages/like.js';
import * as ep___pages_show from './endpoints/pages/show.js';
import * as ep___pages_unlike from './endpoints/pages/unlike.js';
import * as ep___pages_update from './endpoints/pages/update.js';
+import * as ep___flash_create from './endpoints/flash/create.js';
+import * as ep___flash_delete from './endpoints/flash/delete.js';
+import * as ep___flash_featured from './endpoints/flash/featured.js';
+import * as ep___flash_like from './endpoints/flash/like.js';
+import * as ep___flash_show from './endpoints/flash/show.js';
+import * as ep___flash_unlike from './endpoints/flash/unlike.js';
+import * as ep___flash_update from './endpoints/flash/update.js';
+import * as ep___flash_my from './endpoints/flash/my.js';
+import * as ep___flash_myLikes from './endpoints/flash/my-likes.js';
import * as ep___ping from './endpoints/ping.js';
import * as ep___pinnedUsers from './endpoints/pinned-users.js';
import * as ep___promo_read from './endpoints/promo/read.js';
@@ -584,6 +593,15 @@ const eps = [
['pages/show', ep___pages_show],
['pages/unlike', ep___pages_unlike],
['pages/update', ep___pages_update],
+ ['flash/create', ep___flash_create],
+ ['flash/delete', ep___flash_delete],
+ ['flash/featured', ep___flash_featured],
+ ['flash/like', ep___flash_like],
+ ['flash/show', ep___flash_show],
+ ['flash/unlike', ep___flash_unlike],
+ ['flash/update', ep___flash_update],
+ ['flash/my', ep___flash_my],
+ ['flash/my-likes', ep___flash_myLikes],
['ping', ep___ping],
['pinned-users', ep___pinnedUsers],
['promo/read', ep___promo_read],
diff --git a/packages/backend/src/server/api/endpoints/flash/create.ts b/packages/backend/src/server/api/endpoints/flash/create.ts
new file mode 100644
index 0000000000..a652047d98
--- /dev/null
+++ b/packages/backend/src/server/api/endpoints/flash/create.ts
@@ -0,0 +1,66 @@
+import ms from 'ms';
+import { Inject, Injectable } from '@nestjs/common';
+import type { DriveFilesRepository, FlashsRepository, PagesRepository } from '@/models/index.js';
+import { IdService } from '@/core/IdService.js';
+import { Page } from '@/models/entities/Page.js';
+import { Endpoint } from '@/server/api/endpoint-base.js';
+import { PageEntityService } from '@/core/entities/PageEntityService.js';
+import { DI } from '@/di-symbols.js';
+import { FlashEntityService } from '@/core/entities/FlashEntityService.js';
+import { ApiError } from '../../error.js';
+
+export const meta = {
+ tags: ['flash'],
+
+ requireCredential: true,
+
+ kind: 'write:flash',
+
+ limit: {
+ duration: ms('1hour'),
+ max: 10,
+ },
+
+ errors: {
+ },
+} as const;
+
+export const paramDef = {
+ type: 'object',
+ properties: {
+ title: { type: 'string' },
+ summary: { type: 'string' },
+ script: { type: 'string' },
+ permissions: { type: 'array', items: {
+ type: 'string',
+ } },
+ },
+ required: ['title', 'summary', 'script', 'permissions'],
+} as const;
+
+// eslint-disable-next-line import/no-default-export
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.flashsRepository)
+ private flashsRepository: FlashsRepository,
+
+ private flashEntityService: FlashEntityService,
+ private idService: IdService,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ const flash = await this.flashsRepository.insert({
+ id: this.idService.genId(),
+ userId: me.id,
+ createdAt: new Date(),
+ updatedAt: new Date(),
+ title: ps.title,
+ summary: ps.summary,
+ script: ps.script,
+ permissions: ps.permissions,
+ }).then(x => this.flashsRepository.findOneByOrFail(x.identifiers[0]));
+
+ return await this.flashEntityService.pack(flash);
+ });
+ }
+}
diff --git a/packages/backend/src/server/api/endpoints/flash/delete.ts b/packages/backend/src/server/api/endpoints/flash/delete.ts
new file mode 100644
index 0000000000..e94ede9f68
--- /dev/null
+++ b/packages/backend/src/server/api/endpoints/flash/delete.ts
@@ -0,0 +1,56 @@
+import { Inject, Injectable } from '@nestjs/common';
+import type { FlashsRepository } from '@/models/index.js';
+import { Endpoint } from '@/server/api/endpoint-base.js';
+import { DI } from '@/di-symbols.js';
+import { ApiError } from '../../error.js';
+
+export const meta = {
+ tags: ['flashs'],
+
+ requireCredential: true,
+
+ kind: 'write:flash',
+
+ errors: {
+ noSuchFlash: {
+ message: 'No such flash.',
+ code: 'NO_SUCH_FLASH',
+ id: 'de1623ef-bbb3-4289-a71e-14cfa83d9740',
+ },
+
+ accessDenied: {
+ message: 'Access denied.',
+ code: 'ACCESS_DENIED',
+ id: '1036ad7b-9f92-4fff-89c3-0e50dc941704',
+ },
+ },
+} as const;
+
+export const paramDef = {
+ type: 'object',
+ properties: {
+ flashId: { type: 'string', format: 'misskey:id' },
+ },
+ required: ['flashId'],
+} as const;
+
+// eslint-disable-next-line import/no-default-export
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.flashsRepository)
+ private flashsRepository: FlashsRepository,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ const flash = await this.flashsRepository.findOneBy({ id: ps.flashId });
+ if (flash == null) {
+ throw new ApiError(meta.errors.noSuchFlash);
+ }
+ if (flash.userId !== me.id) {
+ throw new ApiError(meta.errors.accessDenied);
+ }
+
+ await this.flashsRepository.delete(flash.id);
+ });
+ }
+}
diff --git a/packages/backend/src/server/api/endpoints/flash/featured.ts b/packages/backend/src/server/api/endpoints/flash/featured.ts
new file mode 100644
index 0000000000..570aef96d2
--- /dev/null
+++ b/packages/backend/src/server/api/endpoints/flash/featured.ts
@@ -0,0 +1,48 @@
+import { Inject, Injectable } from '@nestjs/common';
+import type { FlashsRepository } from '@/models/index.js';
+import { Endpoint } from '@/server/api/endpoint-base.js';
+import { FlashEntityService } from '@/core/entities/FlashEntityService.js';
+import { DI } from '@/di-symbols.js';
+
+export const meta = {
+ tags: ['flash'],
+
+ requireCredential: false,
+
+ res: {
+ type: 'array',
+ optional: false, nullable: false,
+ items: {
+ type: 'object',
+ optional: false, nullable: false,
+ ref: 'Flash',
+ },
+ },
+} as const;
+
+export const paramDef = {
+ type: 'object',
+ properties: {},
+ required: [],
+} as const;
+
+// eslint-disable-next-line import/no-default-export
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.flashsRepository)
+ private flashsRepository: FlashsRepository,
+
+ private flashEntityService: FlashEntityService,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ const query = this.flashsRepository.createQueryBuilder('flash')
+ .andWhere('flash.likedCount > 0')
+ .orderBy('flash.likedCount', 'DESC');
+
+ const flashs = await query.take(10).getMany();
+
+ return await this.flashEntityService.packMany(flashs, me);
+ });
+ }
+}
diff --git a/packages/backend/src/server/api/endpoints/flash/like.ts b/packages/backend/src/server/api/endpoints/flash/like.ts
new file mode 100644
index 0000000000..5581b8ec60
--- /dev/null
+++ b/packages/backend/src/server/api/endpoints/flash/like.ts
@@ -0,0 +1,87 @@
+import { Inject, Injectable } from '@nestjs/common';
+import type { FlashsRepository, FlashLikesRepository } from '@/models/index.js';
+import { IdService } from '@/core/IdService.js';
+import { Endpoint } from '@/server/api/endpoint-base.js';
+import { DI } from '@/di-symbols.js';
+import { ApiError } from '../../error.js';
+
+export const meta = {
+ tags: ['flash'],
+
+ requireCredential: true,
+
+ kind: 'write:flash-likes',
+
+ errors: {
+ noSuchFlash: {
+ message: 'No such flash.',
+ code: 'NO_SUCH_FLASH',
+ id: 'c07c1491-9161-4c5c-9d75-01906f911f73',
+ },
+
+ yourFlash: {
+ message: 'You cannot like your flash.',
+ code: 'YOUR_FLASH',
+ id: '3fd8a0e7-5955-4ba9-85bb-bf3e0c30e13b',
+ },
+
+ alreadyLiked: {
+ message: 'The flash has already been liked.',
+ code: 'ALREADY_LIKED',
+ id: '010065cf-ad43-40df-8067-abff9f4686e3',
+ },
+ },
+} as const;
+
+export const paramDef = {
+ type: 'object',
+ properties: {
+ flashId: { type: 'string', format: 'misskey:id' },
+ },
+ required: ['flashId'],
+} as const;
+
+// eslint-disable-next-line import/no-default-export
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.flashsRepository)
+ private flashsRepository: FlashsRepository,
+
+ @Inject(DI.flashLikesRepository)
+ private flashLikesRepository: FlashLikesRepository,
+
+ private idService: IdService,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ const flash = await this.flashsRepository.findOneBy({ id: ps.flashId });
+ if (flash == null) {
+ throw new ApiError(meta.errors.noSuchFlash);
+ }
+
+ if (flash.userId === me.id) {
+ throw new ApiError(meta.errors.yourFlash);
+ }
+
+ // if already liked
+ const exist = await this.flashLikesRepository.findOneBy({
+ flashId: flash.id,
+ userId: me.id,
+ });
+
+ if (exist != null) {
+ throw new ApiError(meta.errors.alreadyLiked);
+ }
+
+ // Create like
+ await this.flashLikesRepository.insert({
+ id: this.idService.genId(),
+ createdAt: new Date(),
+ flashId: flash.id,
+ userId: me.id,
+ });
+
+ this.flashsRepository.increment({ id: flash.id }, 'likedCount', 1);
+ });
+ }
+}
diff --git a/packages/backend/src/server/api/endpoints/flash/my-likes.ts b/packages/backend/src/server/api/endpoints/flash/my-likes.ts
new file mode 100644
index 0000000000..f7716ea74a
--- /dev/null
+++ b/packages/backend/src/server/api/endpoints/flash/my-likes.ts
@@ -0,0 +1,68 @@
+import { Inject, Injectable } from '@nestjs/common';
+import { Endpoint } from '@/server/api/endpoint-base.js';
+import type { FlashLikesRepository } from '@/models/index.js';
+import { QueryService } from '@/core/QueryService.js';
+import { FlashLikeEntityService } from '@/core/entities/FlashLikeEntityService.js';
+import { DI } from '@/di-symbols.js';
+
+export const meta = {
+ tags: ['account', 'flash'],
+
+ requireCredential: true,
+
+ kind: 'read:flash-likes',
+
+ res: {
+ type: 'array',
+ optional: false, nullable: false,
+ items: {
+ type: 'object',
+ properties: {
+ id: {
+ type: 'string',
+ optional: false, nullable: false,
+ format: 'id',
+ },
+ flash: {
+ type: 'object',
+ optional: false, nullable: false,
+ ref: 'Flash',
+ },
+ },
+ },
+ },
+} as const;
+
+export const paramDef = {
+ type: 'object',
+ properties: {
+ limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
+ sinceId: { type: 'string', format: 'misskey:id' },
+ untilId: { type: 'string', format: 'misskey:id' },
+ },
+ required: [],
+} as const;
+
+// eslint-disable-next-line import/no-default-export
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.flashLikesRepository)
+ private flashLikesRepository: FlashLikesRepository,
+
+ private flashLikeEntityService: FlashLikeEntityService,
+ private queryService: QueryService,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ const query = this.queryService.makePaginationQuery(this.flashLikesRepository.createQueryBuilder('like'), ps.sinceId, ps.untilId)
+ .andWhere('like.userId = :meId', { meId: me.id })
+ .leftJoinAndSelect('like.flash', 'flash');
+
+ const likes = await query
+ .take(ps.limit)
+ .getMany();
+
+ return this.flashLikeEntityService.packMany(likes, me);
+ });
+ }
+}
diff --git a/packages/backend/src/server/api/endpoints/flash/my.ts b/packages/backend/src/server/api/endpoints/flash/my.ts
new file mode 100644
index 0000000000..baed7f000f
--- /dev/null
+++ b/packages/backend/src/server/api/endpoints/flash/my.ts
@@ -0,0 +1,57 @@
+import { Inject, Injectable } from '@nestjs/common';
+import { Endpoint } from '@/server/api/endpoint-base.js';
+import type { FlashsRepository } from '@/models/index.js';
+import { QueryService } from '@/core/QueryService.js';
+import { FlashEntityService } from '@/core/entities/FlashEntityService.js';
+import { DI } from '@/di-symbols.js';
+
+export const meta = {
+ tags: ['account', 'flash'],
+
+ requireCredential: true,
+
+ kind: 'read:flash',
+
+ res: {
+ type: 'array',
+ optional: false, nullable: false,
+ items: {
+ type: 'object',
+ optional: false, nullable: false,
+ ref: 'Flash',
+ },
+ },
+} as const;
+
+export const paramDef = {
+ type: 'object',
+ properties: {
+ limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
+ sinceId: { type: 'string', format: 'misskey:id' },
+ untilId: { type: 'string', format: 'misskey:id' },
+ },
+ required: [],
+} as const;
+
+// eslint-disable-next-line import/no-default-export
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.flashsRepository)
+ private flashsRepository: FlashsRepository,
+
+ private flashEntityService: FlashEntityService,
+ private queryService: QueryService,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ const query = this.queryService.makePaginationQuery(this.flashsRepository.createQueryBuilder('flash'), ps.sinceId, ps.untilId)
+ .andWhere('flash.userId = :meId', { meId: me.id });
+
+ const flashs = await query
+ .take(ps.limit)
+ .getMany();
+
+ return await this.flashEntityService.packMany(flashs);
+ });
+ }
+}
diff --git a/packages/backend/src/server/api/endpoints/flash/show.ts b/packages/backend/src/server/api/endpoints/flash/show.ts
new file mode 100644
index 0000000000..48114c5a60
--- /dev/null
+++ b/packages/backend/src/server/api/endpoints/flash/show.ts
@@ -0,0 +1,60 @@
+import { IsNull } from 'typeorm';
+import { Inject, Injectable } from '@nestjs/common';
+import type { UsersRepository, FlashsRepository } from '@/models/index.js';
+import type { Flash } from '@/models/entities/Flash.js';
+import { Endpoint } from '@/server/api/endpoint-base.js';
+import { FlashEntityService } from '@/core/entities/FlashEntityService.js';
+import { DI } from '@/di-symbols.js';
+import { ApiError } from '../../error.js';
+
+export const meta = {
+ tags: ['flashs'],
+
+ requireCredential: false,
+
+ res: {
+ type: 'object',
+ optional: false, nullable: false,
+ ref: 'Flash',
+ },
+
+ errors: {
+ noSuchFlash: {
+ message: 'No such flash.',
+ code: 'NO_SUCH_FLASH',
+ id: 'f0d34a1a-d29a-401d-90ba-1982122b5630',
+ },
+ },
+} as const;
+
+export const paramDef = {
+ type: 'object',
+ properties: {
+ flashId: { type: 'string', format: 'misskey:id' },
+ },
+ required: ['flashId'],
+} as const;
+
+// eslint-disable-next-line import/no-default-export
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.usersRepository)
+ private usersRepository: UsersRepository,
+
+ @Inject(DI.flashsRepository)
+ private flashsRepository: FlashsRepository,
+
+ private flashEntityService: FlashEntityService,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ const flash = await this.flashsRepository.findOneBy({ id: ps.flashId });
+
+ if (flash == null) {
+ throw new ApiError(meta.errors.noSuchFlash);
+ }
+
+ return await this.flashEntityService.pack(flash, me);
+ });
+ }
+}
diff --git a/packages/backend/src/server/api/endpoints/flash/unlike.ts b/packages/backend/src/server/api/endpoints/flash/unlike.ts
new file mode 100644
index 0000000000..b994f5d347
--- /dev/null
+++ b/packages/backend/src/server/api/endpoints/flash/unlike.ts
@@ -0,0 +1,68 @@
+import { Inject, Injectable } from '@nestjs/common';
+import type { FlashsRepository, FlashLikesRepository } from '@/models/index.js';
+import { Endpoint } from '@/server/api/endpoint-base.js';
+import { DI } from '@/di-symbols.js';
+import { ApiError } from '../../error.js';
+
+export const meta = {
+ tags: ['flash'],
+
+ requireCredential: true,
+
+ kind: 'write:flash-likes',
+
+ errors: {
+ noSuchFlash: {
+ message: 'No such flash.',
+ code: 'NO_SUCH_FLASH',
+ id: 'afe8424a-a69e-432d-a5f2-2f0740c62410',
+ },
+
+ notLiked: {
+ message: 'You have not liked that flash.',
+ code: 'NOT_LIKED',
+ id: '755f25a7-9871-4f65-9f34-51eaad9ae0ac',
+ },
+ },
+} as const;
+
+export const paramDef = {
+ type: 'object',
+ properties: {
+ flashId: { type: 'string', format: 'misskey:id' },
+ },
+ required: ['flashId'],
+} as const;
+
+// eslint-disable-next-line import/no-default-export
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.flashsRepository)
+ private flashsRepository: FlashsRepository,
+
+ @Inject(DI.flashLikesRepository)
+ private flashLikesRepository: FlashLikesRepository,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ const flash = await this.flashsRepository.findOneBy({ id: ps.flashId });
+ if (flash == null) {
+ throw new ApiError(meta.errors.noSuchFlash);
+ }
+
+ const exist = await this.flashLikesRepository.findOneBy({
+ flashId: flash.id,
+ userId: me.id,
+ });
+
+ if (exist == null) {
+ throw new ApiError(meta.errors.notLiked);
+ }
+
+ // Delete like
+ await this.flashLikesRepository.delete(exist.id);
+
+ this.flashsRepository.decrement({ id: flash.id }, 'likedCount', 1);
+ });
+ }
+}
diff --git a/packages/backend/src/server/api/endpoints/flash/update.ts b/packages/backend/src/server/api/endpoints/flash/update.ts
new file mode 100644
index 0000000000..9ab17a61e8
--- /dev/null
+++ b/packages/backend/src/server/api/endpoints/flash/update.ts
@@ -0,0 +1,78 @@
+import ms from 'ms';
+import { Not } from 'typeorm';
+import { Inject, Injectable } from '@nestjs/common';
+import type { FlashsRepository, DriveFilesRepository } from '@/models/index.js';
+import { Endpoint } from '@/server/api/endpoint-base.js';
+import { DI } from '@/di-symbols.js';
+import { ApiError } from '../../error.js';
+
+export const meta = {
+ tags: ['flash'],
+
+ requireCredential: true,
+
+ kind: 'write:flash',
+
+ limit: {
+ duration: ms('1hour'),
+ max: 300,
+ },
+
+ errors: {
+ noSuchFlash: {
+ message: 'No such flash.',
+ code: 'NO_SUCH_FLASH',
+ id: '611e13d2-309e-419a-a5e4-e0422da39b02',
+ },
+
+ accessDenied: {
+ message: 'Access denied.',
+ code: 'ACCESS_DENIED',
+ id: '08e60c88-5948-478e-a132-02ec701d67b2',
+ },
+ },
+} as const;
+
+export const paramDef = {
+ type: 'object',
+ properties: {
+ flashId: { type: 'string', format: 'misskey:id' },
+ title: { type: 'string' },
+ summary: { type: 'string' },
+ script: { type: 'string' },
+ permissions: { type: 'array', items: {
+ type: 'string',
+ } },
+ },
+ required: ['flashId', 'title', 'summary', 'script', 'permissions'],
+} as const;
+
+// eslint-disable-next-line import/no-default-export
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.flashsRepository)
+ private flashsRepository: FlashsRepository,
+
+ @Inject(DI.driveFilesRepository)
+ private driveFilesRepository: DriveFilesRepository,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ const flash = await this.flashsRepository.findOneBy({ id: ps.flashId });
+ if (flash == null) {
+ throw new ApiError(meta.errors.noSuchFlash);
+ }
+ if (flash.userId !== me.id) {
+ throw new ApiError(meta.errors.accessDenied);
+ }
+
+ await this.flashsRepository.update(flash.id, {
+ updatedAt: new Date(),
+ title: ps.title,
+ summary: ps.summary,
+ script: ps.script,
+ permissions: ps.permissions,
+ });
+ });
+ }
+}