summaryrefslogtreecommitdiff
path: root/packages/backend/src/models
diff options
context:
space:
mode:
authormisskey-release-bot[bot] <157398866+misskey-release-bot[bot]@users.noreply.github.com>2025-07-18 00:28:01 +0000
committerGitHub <noreply@github.com>2025-07-18 00:28:01 +0000
commite86e9b46b3c30391296f04842529f9f8411dcbba (patch)
treebbe0f31106c393c1a742183aedd1c39ccdfa39a2 /packages/backend/src/models
parentMerge pull request #16197 from misskey-dev/develop (diff)
parentRelease: 2025.7.0 (diff)
downloadmisskey-e86e9b46b3c30391296f04842529f9f8411dcbba.tar.gz
misskey-e86e9b46b3c30391296f04842529f9f8411dcbba.tar.bz2
misskey-e86e9b46b3c30391296f04842529f9f8411dcbba.zip
Merge pull request #16244 from misskey-dev/develop
Release: 2025.7.0
Diffstat (limited to 'packages/backend/src/models')
-rw-r--r--packages/backend/src/models/AbuseReportNotificationRecipient.ts16
-rw-r--r--packages/backend/src/models/Emoji.ts2
-rw-r--r--packages/backend/src/models/Meta.ts7
-rw-r--r--packages/backend/src/models/Note.ts7
-rw-r--r--packages/backend/src/models/NoteDraft.ts163
-rw-r--r--packages/backend/src/models/RepositoryModule.ts9
-rw-r--r--packages/backend/src/models/User.ts2
-rw-r--r--packages/backend/src/models/UserProfile.ts2
-rw-r--r--packages/backend/src/models/_.ts3
-rw-r--r--packages/backend/src/models/json-schema/flash.ts2
-rw-r--r--packages/backend/src/models/json-schema/note-draft.ts171
-rw-r--r--packages/backend/src/models/json-schema/role.ts8
12 files changed, 377 insertions, 15 deletions
diff --git a/packages/backend/src/models/AbuseReportNotificationRecipient.ts b/packages/backend/src/models/AbuseReportNotificationRecipient.ts
index fbff880afc..17ec6abed5 100644
--- a/packages/backend/src/models/AbuseReportNotificationRecipient.ts
+++ b/packages/backend/src/models/AbuseReportNotificationRecipient.ts
@@ -22,7 +22,7 @@ export class MiAbuseReportNotificationRecipient {
/**
* 有効かどうか.
*/
- @Index()
+ @Index('IDX_abuse_report_notification_recipient_isActive')
@Column('boolean', {
default: true,
})
@@ -47,7 +47,7 @@ export class MiAbuseReportNotificationRecipient {
/**
* 通知方法.
*/
- @Index()
+ @Index('IDX_abuse_report_notification_recipient_method')
@Column('varchar', {
length: 64,
})
@@ -56,10 +56,11 @@ export class MiAbuseReportNotificationRecipient {
/**
* 通知先のユーザID.
*/
- @Index()
+ @Index('IDX_abuse_report_notification_recipient_userId')
@Column({
...id(),
nullable: true,
+ default: null,
})
public userId: MiUser['id'] | null;
@@ -75,17 +76,20 @@ export class MiAbuseReportNotificationRecipient {
/**
* 通知先のユーザプロフィール.
*/
- @ManyToOne(type => MiUserProfile, {})
+ @ManyToOne(type => MiUserProfile, {
+ onDelete: 'CASCADE',
+ })
@JoinColumn({ name: 'userId', referencedColumnName: 'userId', foreignKeyConstraintName: 'FK_abuse_report_notification_recipient_userId2' })
public userProfile: MiUserProfile | null;
/**
* 通知先のシステムWebhookId.
*/
- @Index()
+ @Index('IDX_abuse_report_notification_recipient_systemWebhookId')
@Column({
...id(),
nullable: true,
+ default: null,
})
public systemWebhookId: string | null;
@@ -95,6 +99,6 @@ export class MiAbuseReportNotificationRecipient {
@ManyToOne(type => MiSystemWebhook, {
onDelete: 'CASCADE',
})
- @JoinColumn()
+ @JoinColumn({ name: 'systemWebhookId', referencedColumnName: 'id', foreignKeyConstraintName: 'FK_abuse_report_notification_recipient_systemWebhookId' })
public systemWebhook: MiSystemWebhook | null;
}
diff --git a/packages/backend/src/models/Emoji.ts b/packages/backend/src/models/Emoji.ts
index d62b6e9f6f..8dff8fd153 100644
--- a/packages/backend/src/models/Emoji.ts
+++ b/packages/backend/src/models/Emoji.ts
@@ -8,6 +8,7 @@ import { id } from './util/id.js';
@Entity('emoji')
@Index(['name', 'host'], { unique: true })
+@Index('IDX_EMOJI_ROLE_IDS', { synchronize: false }) // GIN for roleIdsThatCanBeUsedThisEmojiAsReaction in production
export class MiEmoji {
@PrimaryColumn(id())
public id: string;
@@ -32,6 +33,7 @@ export class MiEmoji {
@Column('varchar', {
length: 128, nullable: true,
})
+ @Index('IDX_EMOJI_CATEGORY')
public category: string | null;
@Column('varchar', {
diff --git a/packages/backend/src/models/Meta.ts b/packages/backend/src/models/Meta.ts
index 3ee6190d45..85c10ab666 100644
--- a/packages/backend/src/models/Meta.ts
+++ b/packages/backend/src/models/Meta.ts
@@ -59,7 +59,7 @@ export class MiMeta {
public maintainerEmail: string | null;
@Column('boolean', {
- default: false,
+ default: true,
})
public disableRegistration: boolean;
@@ -570,7 +570,7 @@ export class MiMeta {
public bannedEmailDomains: string[];
@Column('varchar', {
- length: 1024, array: true, default: '{ "admin", "administrator", "root", "system", "maintainer", "host", "mod", "moderator", "owner", "superuser", "staff", "auth", "i", "me", "everyone", "all", "mention", "mentions", "example", "user", "users", "account", "accounts", "official", "help", "helps", "support", "supports", "info", "information", "informations", "announce", "announces", "announcement", "announcements", "notice", "notification", "notifications", "dev", "developer", "developers", "tech", "misskey" }',
+ length: 1024, array: true, default: ['admin', 'administrator', 'root', 'system', 'maintainer', 'host', 'mod', 'moderator', 'owner', 'superuser', 'staff', 'auth', 'i', 'me', 'everyone', 'all', 'mention', 'mentions', 'example', 'user', 'users', 'account', 'accounts', 'official', 'help', 'helps', 'support', 'supports', 'info', 'information', 'informations', 'announce', 'announces', 'announcement', 'announcements', 'notice', 'notification', 'notifications', 'dev', 'developer', 'developers', 'tech', 'misskey'],
})
public preservedUsernames: string[];
@@ -635,7 +635,7 @@ export class MiMeta {
public urlPreviewMaximumContentLength: number;
@Column('boolean', {
- default: true,
+ default: false,
})
public urlPreviewRequireContentLength: boolean;
@@ -648,6 +648,7 @@ export class MiMeta {
@Column('varchar', {
length: 1024,
nullable: true,
+ default: null,
})
public urlPreviewUserAgent: string | null;
diff --git a/packages/backend/src/models/Note.ts b/packages/backend/src/models/Note.ts
index 3dcbdb735b..9822ec94e4 100644
--- a/packages/backend/src/models/Note.ts
+++ b/packages/backend/src/models/Note.ts
@@ -4,7 +4,7 @@
*/
import { Entity, Index, JoinColumn, Column, PrimaryColumn, ManyToOne } from 'typeorm';
-import { noteVisibilities } from '@/types.js';
+import { noteVisibilities, noteReactionAcceptances } from '@/types.js';
import { id } from './util/id.js';
import { MiUser } from './User.js';
import { MiChannel } from './Channel.js';
@@ -20,7 +20,8 @@ import type { MiDriveFile } from './DriveFile.js';
// You should not use `@Index({ concurrent: true })` decorator because database initialization for test will fail
// because it will always run CREATE INDEX in transaction based on decorators.
// Not appending `{ concurrent: true }` to `@Index` will not cause any problem in production,
-@Index(['userId', 'id'])
+
+@Index(['userId', 'id']) // Note: this index is ("userId", "id" DESC) in production, but not in test.
@Entity('note')
export class MiNote {
@PrimaryColumn(id())
@@ -96,7 +97,7 @@ export class MiNote {
@Column('varchar', {
length: 64, nullable: true,
})
- public reactionAcceptance: 'likeOnly' | 'likeOnlyForRemote' | 'nonSensitiveOnly' | 'nonSensitiveOnlyForLocalLikeOnlyForRemote' | null;
+ public reactionAcceptance: typeof noteReactionAcceptances[number];
@Column('smallint', {
default: 0,
diff --git a/packages/backend/src/models/NoteDraft.ts b/packages/backend/src/models/NoteDraft.ts
new file mode 100644
index 0000000000..6483748bc2
--- /dev/null
+++ b/packages/backend/src/models/NoteDraft.ts
@@ -0,0 +1,163 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import { Entity, Index, JoinColumn, Column, PrimaryColumn, ManyToOne } from 'typeorm';
+import { noteVisibilities, noteReactionAcceptances } from '@/types.js';
+import { id } from './util/id.js';
+import { MiUser } from './User.js';
+import { MiChannel } from './Channel.js';
+import { MiNote } from './Note.js';
+import type { MiDriveFile } from './DriveFile.js';
+
+@Entity('note_draft')
+@Index('IDX_NOTE_DRAFT_FILE_IDS', { synchronize: false }) // GIN for fileIds in production
+@Index('IDX_NOTE_DRAFT_VISIBLE_USER_IDS', { synchronize: false }) // GIN for visibleUserIds in production
+export class MiNoteDraft {
+ @PrimaryColumn(id())
+ public id: string;
+
+ @Index('IDX_NOTE_DRAFT_REPLY_ID')
+ @Column({
+ ...id(),
+ nullable: true,
+ comment: 'The ID of reply target.',
+ })
+ public replyId: MiNote['id'] | null;
+
+ // There is a possibility that replyId is not null but reply is null when the reply note is deleted.
+ @ManyToOne(type => MiNote, {
+ createForeignKeyConstraints: false,
+ })
+ @JoinColumn()
+ public reply: MiNote | null;
+
+ @Index('IDX_NOTE_DRAFT_RENOTE_ID')
+ @Column({
+ ...id(),
+ nullable: true,
+ comment: 'The ID of renote target.',
+ })
+ public renoteId: MiNote['id'] | null;
+
+ // There is a possibility that renoteId is not null but renote is null when the renote note is deleted.
+ @ManyToOne(type => MiNote, {
+ createForeignKeyConstraints: false,
+ })
+ @JoinColumn()
+ public renote: MiNote | null;
+
+ // TODO: varcharにしたい(Note.tsと同じ)
+ @Column('text', {
+ nullable: true,
+ })
+ public text: string | null;
+
+ @Column('varchar', {
+ length: 512, nullable: true,
+ })
+ public cw: string | null;
+
+ @Index('IDX_NOTE_DRAFT_USER_ID')
+ @Column({
+ ...id(),
+ comment: 'The ID of author.',
+ })
+ public userId: MiUser['id'];
+
+ @ManyToOne(type => MiUser, {
+ onDelete: 'CASCADE',
+ })
+ @JoinColumn()
+ public user: MiUser | null;
+
+ @Column('boolean', {
+ default: false,
+ })
+ public localOnly: boolean;
+
+ @Column('varchar', {
+ length: 64, nullable: true,
+ })
+ public reactionAcceptance: typeof noteReactionAcceptances[number];
+
+ /**
+ * public ... 公開
+ * home ... ホームタイムライン(ユーザーページのタイムライン含む)のみに流す
+ * followers ... フォロワーのみ
+ * specified ... visibleUserIds で指定したユーザーのみ
+ */
+ @Column('enum', { enum: noteVisibilities })
+ public visibility: typeof noteVisibilities[number];
+
+ @Index('IDX_NOTE_DRAFT_FILE_IDS', { synchronize: false })
+ @Column({
+ ...id(),
+ array: true, default: '{}',
+ })
+ public fileIds: MiDriveFile['id'][];
+
+ @Index('IDX_NOTE_DRAFT_VISIBLE_USER_IDS', { synchronize: false })
+ @Column({
+ ...id(),
+ array: true, default: '{}',
+ })
+ public visibleUserIds: MiUser['id'][];
+
+ @Column('varchar', {
+ length: 128, nullable: true,
+ })
+ public hashtag: string | null;
+
+ @Index('IDX_NOTE_DRAFT_CHANNEL_ID')
+ @Column({
+ ...id(),
+ nullable: true,
+ comment: 'The ID of source channel.',
+ })
+ public channelId: MiChannel['id'] | null;
+
+ // There is a possibility that channelId is not null but channel is null when the channel is deleted.
+ // (deleting channel is not implemented so it's not happening now but may happen in the future)
+ @ManyToOne(type => MiChannel, {
+ createForeignKeyConstraints: false,
+ })
+ @JoinColumn()
+ public channel: MiChannel | null;
+
+ // 以下、Pollについて追加
+
+ @Column('boolean', {
+ default: false,
+ })
+ public hasPoll: boolean;
+
+ @Column('varchar', {
+ length: 256, array: true, default: '{}',
+ })
+ public pollChoices: string[];
+
+ @Column('boolean')
+ public pollMultiple: boolean;
+
+ @Column('timestamp with time zone', {
+ nullable: true,
+ })
+ public pollExpiresAt: Date | null;
+
+ @Column('bigint', {
+ nullable: true,
+ })
+ public pollExpiredAfter: number | null;
+
+ // ここまで追加
+
+ constructor(data: Partial<MiNoteDraft>) {
+ if (data == null) return;
+
+ for (const [k, v] of Object.entries(data)) {
+ (this as any)[k] = v;
+ }
+ }
+}
diff --git a/packages/backend/src/models/RepositoryModule.ts b/packages/backend/src/models/RepositoryModule.ts
index b7142d91bf..146dbbc3b8 100644
--- a/packages/backend/src/models/RepositoryModule.ts
+++ b/packages/backend/src/models/RepositoryModule.ts
@@ -42,6 +42,7 @@ import {
MiNoteFavorite,
MiNoteReaction,
MiNoteThreadMuting,
+ MiNoteDraft,
MiPage,
MiPageLike,
MiPasswordResetRequest,
@@ -140,6 +141,12 @@ const $noteReactionsRepository: Provider = {
inject: [DI.db],
};
+const $noteDraftsRepository: Provider = {
+ provide: DI.noteDraftsRepository,
+ useFactory: (db: DataSource) => db.getRepository(MiNoteDraft).extend(miRepository as MiRepository<MiNoteDraft>),
+ inject: [DI.db],
+};
+
const $pollsRepository: Provider = {
provide: DI.pollsRepository,
useFactory: (db: DataSource) => db.getRepository(MiPoll).extend(miRepository as MiRepository<MiPoll>),
@@ -542,6 +549,7 @@ const $reversiGamesRepository: Provider = {
$noteFavoritesRepository,
$noteThreadMutingsRepository,
$noteReactionsRepository,
+ $noteDraftsRepository,
$pollsRepository,
$pollVotesRepository,
$userProfilesRepository,
@@ -618,6 +626,7 @@ const $reversiGamesRepository: Provider = {
$noteFavoritesRepository,
$noteThreadMutingsRepository,
$noteReactionsRepository,
+ $noteDraftsRepository,
$pollsRepository,
$pollVotesRepository,
$userProfilesRepository,
diff --git a/packages/backend/src/models/User.ts b/packages/backend/src/models/User.ts
index baf4eefdf1..a6e9edcf5f 100644
--- a/packages/backend/src/models/User.ts
+++ b/packages/backend/src/models/User.ts
@@ -120,7 +120,7 @@ export class MiUser {
// avatarId が null になったとしてもこれが null でない可能性があるため、このフィールドを使うときは avatarId の non-null チェックをすること
@Column('varchar', {
- length: 512, nullable: true,
+ length: 1024, nullable: true,
})
public avatarUrl: string | null;
diff --git a/packages/backend/src/models/UserProfile.ts b/packages/backend/src/models/UserProfile.ts
index c4c1fa5ec9..501b539210 100644
--- a/packages/backend/src/models/UserProfile.ts
+++ b/packages/backend/src/models/UserProfile.ts
@@ -29,7 +29,7 @@ export class MiUserProfile {
})
public location: string | null;
- @Index()
+ // Note: There's index named IDX_de22cd2b445eee31ae51cdbe99 for SUBSTR("birthday", 6, 5)
@Column('char', {
length: 10, nullable: true,
comment: 'The birthday (YYYY-MM-DD) of the User.',
diff --git a/packages/backend/src/models/_.ts b/packages/backend/src/models/_.ts
index e1ea2a2604..84b5cbed0a 100644
--- a/packages/backend/src/models/_.ts
+++ b/packages/backend/src/models/_.ts
@@ -55,6 +55,7 @@ import { MiMeta } from '@/models/Meta.js';
import { MiModerationLog } from '@/models/ModerationLog.js';
import { MiMuting } from '@/models/Muting.js';
import { MiNote } from '@/models/Note.js';
+import { MiNoteDraft } from '@/models/NoteDraft.js';
import { MiNoteFavorite } from '@/models/NoteFavorite.js';
import { MiNoteReaction } from '@/models/NoteReaction.js';
import { MiNoteThreadMuting } from '@/models/NoteThreadMuting.js';
@@ -188,6 +189,7 @@ export {
MiMuting,
MiRenoteMuting,
MiNote,
+ MiNoteDraft,
MiNoteFavorite,
MiNoteReaction,
MiNoteThreadMuting,
@@ -266,6 +268,7 @@ export type ModerationLogsRepository = Repository<MiModerationLog> & MiRepositor
export type MutingsRepository = Repository<MiMuting> & MiRepository<MiMuting>;
export type RenoteMutingsRepository = Repository<MiRenoteMuting> & MiRepository<MiRenoteMuting>;
export type NotesRepository = Repository<MiNote> & MiRepository<MiNote>;
+export type NoteDraftsRepository = Repository<MiNoteDraft> & MiRepository<MiNoteDraft>;
export type NoteFavoritesRepository = Repository<MiNoteFavorite> & MiRepository<MiNoteFavorite>;
export type NoteReactionsRepository = Repository<MiNoteReaction> & MiRepository<MiNoteReaction>;
export type NoteThreadMutingsRepository = Repository<MiNoteThreadMuting> & MiRepository<MiNoteThreadMuting>;
diff --git a/packages/backend/src/models/json-schema/flash.ts b/packages/backend/src/models/json-schema/flash.ts
index 42b2172409..d50200e6e9 100644
--- a/packages/backend/src/models/json-schema/flash.ts
+++ b/packages/backend/src/models/json-schema/flash.ts
@@ -51,7 +51,7 @@ export const packedFlashSchema = {
},
likedCount: {
type: 'number',
- optional: false, nullable: true,
+ optional: false, nullable: false,
},
isLiked: {
type: 'boolean',
diff --git a/packages/backend/src/models/json-schema/note-draft.ts b/packages/backend/src/models/json-schema/note-draft.ts
new file mode 100644
index 0000000000..504b263a6d
--- /dev/null
+++ b/packages/backend/src/models/json-schema/note-draft.ts
@@ -0,0 +1,171 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+export const packedNoteDraftSchema = {
+ type: 'object',
+ properties: {
+ id: {
+ type: 'string',
+ optional: false, nullable: false,
+ format: 'id',
+ example: 'xxxxxxxxxx',
+ },
+ createdAt: {
+ type: 'string',
+ optional: false, nullable: false,
+ format: 'date-time',
+ },
+ text: {
+ type: 'string',
+ optional: false, nullable: true,
+ },
+ cw: {
+ type: 'string',
+ optional: true, nullable: true,
+ },
+ userId: {
+ type: 'string',
+ optional: false, nullable: false,
+ format: 'id',
+ },
+ user: {
+ type: 'object',
+ ref: 'UserLite',
+ optional: false, nullable: false,
+ },
+ replyId: {
+ type: 'string',
+ optional: true, nullable: true,
+ format: 'id',
+ example: 'xxxxxxxxxx',
+ },
+ renoteId: {
+ type: 'string',
+ optional: true, nullable: true,
+ format: 'id',
+ example: 'xxxxxxxxxx',
+ },
+ reply: {
+ type: 'object',
+ optional: true, nullable: true,
+ ref: 'Note',
+ description: 'The reply target note contents if exists. If the reply target has been deleted since the draft was created, this will be null while replyId is not null.',
+ },
+ renote: {
+ type: 'object',
+ optional: true, nullable: true,
+ ref: 'Note',
+ description: 'The renote target note contents if exists. If the renote target has been deleted since the draft was created, this will be null while renoteId is not null.',
+ },
+ visibility: {
+ type: 'string',
+ optional: false, nullable: false,
+ enum: ['public', 'home', 'followers', 'specified'],
+ },
+ visibleUserIds: {
+ type: 'array',
+ optional: true, nullable: false,
+ items: {
+ type: 'string',
+ optional: false, nullable: false,
+ format: 'id',
+ },
+ },
+ fileIds: {
+ type: 'array',
+ optional: true, nullable: false,
+ items: {
+ type: 'string',
+ optional: false, nullable: false,
+ format: 'id',
+ },
+ },
+ files: {
+ type: 'array',
+ optional: true, nullable: false,
+ items: {
+ type: 'object',
+ optional: false, nullable: false,
+ ref: 'DriveFile',
+ },
+ },
+ hashtag: {
+ type: 'string',
+ optional: true, nullable: false,
+ },
+ poll: {
+ type: 'object',
+ optional: true, nullable: true,
+ properties: {
+ expiresAt: {
+ type: 'string',
+ optional: true, nullable: true,
+ format: 'date-time',
+ },
+ expiredAfter: {
+ type: 'number',
+ optional: true, nullable: true,
+ },
+ multiple: {
+ type: 'boolean',
+ optional: false, nullable: false,
+ },
+ choices: {
+ type: 'array',
+ optional: false, nullable: false,
+ items: {
+ type: 'string',
+ optional: false, nullable: false,
+ },
+ },
+ },
+ },
+ channelId: {
+ type: 'string',
+ optional: true, nullable: true,
+ format: 'id',
+ example: 'xxxxxxxxxx',
+ },
+ channel: {
+ type: 'object',
+ optional: true, nullable: true,
+ properties: {
+ id: {
+ type: 'string',
+ optional: false, nullable: false,
+ },
+ name: {
+ type: 'string',
+ optional: false, nullable: false,
+ },
+ color: {
+ type: 'string',
+ optional: false, nullable: false,
+ },
+ isSensitive: {
+ type: 'boolean',
+ optional: false, nullable: false,
+ },
+ allowRenoteToExternal: {
+ type: 'boolean',
+ optional: false, nullable: false,
+ },
+ userId: {
+ type: 'string',
+ optional: false, nullable: true,
+ },
+ },
+ },
+ localOnly: {
+ type: 'boolean',
+ optional: true, nullable: false,
+ },
+ reactionAcceptance: {
+ type: 'string',
+ optional: false, nullable: true,
+ enum: ['likeOnly', 'likeOnlyForRemote', 'nonSensitiveOnly', 'nonSensitiveOnlyForLocalLikeOnlyForRemote', null],
+ },
+ },
+} as const;
diff --git a/packages/backend/src/models/json-schema/role.ts b/packages/backend/src/models/json-schema/role.ts
index 8bd01c92a3..c9cdbd5d89 100644
--- a/packages/backend/src/models/json-schema/role.ts
+++ b/packages/backend/src/models/json-schema/role.ts
@@ -309,6 +309,14 @@ export const packedRolePoliciesSchema = {
optional: false, nullable: false,
enum: ['available', 'readonly', 'unavailable'],
},
+ noteDraftLimit: {
+ type: 'integer',
+ optional: false, nullable: false,
+ },
+ watermarkAvailable: {
+ type: 'boolean',
+ optional: false, nullable: false,
+ },
},
} as const;