Migration cleanup (#16288)

* chore: apply several @Index and @ManyToOne to match actual migration code

* chore: several decorator updates with typeorm bug workaround with patches

* feat: add final cleanup migration

* dev: add .editorconfig settings for generated migrations

* chore: update dockerfile to build package with patches

* chore: update federation test compose to include patches

* chore: revert few dependency update

* chore: don't check disableRegistration on test env

* test: add test for checking migration script

* chore: set proxyRemoteFiles true in test config

* chore: enter invitation code in signup test

* fix: register send button is not disabled when invitationCode is not input
This commit is contained in:
anatawa12 2025-07-16 15:49:05 +09:00 committed by GitHub
parent ad7bf096e1
commit 6f6fdfe28e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
21 changed files with 228 additions and 19 deletions

View File

@ -13,3 +13,7 @@ trim_trailing_whitespace = false
[*.{yml,yaml}] [*.{yml,yaml}]
indent_style = space indent_style = space
[packages/backend/migration/*.js]
indent_style = space
indent_size = 4

View File

@ -15,3 +15,5 @@ redis:
host: 127.0.0.1 host: 127.0.0.1
port: 56312 port: 56312
id: aidx id: aidx
proxyRemoteFiles: true

View File

@ -152,3 +152,47 @@ jobs:
with: with:
token: ${{ secrets.CODECOV_TOKEN }} token: ${{ secrets.CODECOV_TOKEN }}
files: ./packages/backend/coverage/coverage-final.json files: ./packages/backend/coverage/coverage-final.json
migration:
name: Migration tests (backend)
runs-on: ubuntu-latest
strategy:
matrix:
node-version-file:
- .node-version
#- .github/min.node-version
services:
postgres:
image: postgres:15
ports:
- 54312:5432
env:
POSTGRES_DB: test-misskey
POSTGRES_HOST_AUTH_METHOD: trust
steps:
- uses: actions/checkout@v4.2.2
with:
submodules: true
- name: Setup pnpm
uses: pnpm/action-setup@v4.1.0
- name: Get current date
id: current-date
run: echo "today=$(date +'%Y-%m-%d')" >> $GITHUB_OUTPUT
- name: Use Node.js
uses: actions/setup-node@v4.4.0
with:
node-version-file: ${{ matrix.node-version-file }}
cache: 'pnpm'
- run: pnpm i --frozen-lockfile
- name: Check pnpm-lock.yaml
run: git diff --exit-code pnpm-lock.yaml
- name: Copy Configure
run: cp .github/misskey/test.yml .config
- name: Build
run: pnpm build
- name: Run migrations
run: MISSKEY_CONFIG_YML=test.yml pnpm --filter backend migrate
- name: Check no migrations are remaining
run: MISSKEY_CONFIG_YML=test.yml pnpm --filter backend check-migrations

View File

@ -18,6 +18,7 @@ WORKDIR /misskey
COPY --link ["pnpm-lock.yaml", "pnpm-workspace.yaml", "package.json", "./"] COPY --link ["pnpm-lock.yaml", "pnpm-workspace.yaml", "package.json", "./"]
COPY --link ["scripts", "./scripts"] COPY --link ["scripts", "./scripts"]
COPY --link ["patches", "./patches"]
COPY --link ["packages/backend/package.json", "./packages/backend/"] COPY --link ["packages/backend/package.json", "./packages/backend/"]
COPY --link ["packages/frontend-shared/package.json", "./packages/frontend-shared/"] COPY --link ["packages/frontend-shared/package.json", "./packages/frontend-shared/"]
COPY --link ["packages/frontend/package.json", "./packages/frontend/"] COPY --link ["packages/frontend/package.json", "./packages/frontend/"]
@ -53,6 +54,7 @@ WORKDIR /misskey
COPY --link ["pnpm-lock.yaml", "pnpm-workspace.yaml", "package.json", "./"] COPY --link ["pnpm-lock.yaml", "pnpm-workspace.yaml", "package.json", "./"]
COPY --link ["scripts", "./scripts"] COPY --link ["scripts", "./scripts"]
COPY --link ["patches", "./patches"]
COPY --link ["packages/backend/package.json", "./packages/backend/"] COPY --link ["packages/backend/package.json", "./packages/backend/"]
COPY --link ["packages/misskey-js/package.json", "./packages/misskey-js/"] COPY --link ["packages/misskey-js/package.json", "./packages/misskey-js/"]
COPY --link ["packages/misskey-reversi/package.json", "./packages/misskey-reversi/"] COPY --link ["packages/misskey-reversi/package.json", "./packages/misskey-reversi/"]

View File

@ -78,6 +78,8 @@ describe('After setup instance', () => {
cy.get('[data-cy-signup-password] input').type('alice1234'); cy.get('[data-cy-signup-password] input').type('alice1234');
cy.get('[data-cy-signup-submit]').should('be.disabled'); cy.get('[data-cy-signup-submit]').should('be.disabled');
cy.get('[data-cy-signup-password-retype] input').type('alice1234'); cy.get('[data-cy-signup-password-retype] input').type('alice1234');
cy.get('[data-cy-signup-submit]').should('be.disabled');
cy.get('[data-cy-signup-invitation-code] input').type('test-invitation-code');
cy.get('[data-cy-signup-submit]').should('not.be.disabled'); cy.get('[data-cy-signup-submit]').should('not.be.disabled');
cy.get('[data-cy-signup-submit]').click(); cy.get('[data-cy-signup-submit]').click();

View File

@ -83,6 +83,9 @@
"pnpm": { "pnpm": {
"overrides": { "overrides": {
"@aiscript-dev/aiscript-languageserver": "-" "@aiscript-dev/aiscript-languageserver": "-"
},
"patchedDependencies": {
"typeorm": "patches/typeorm.patch"
} }
} }
} }

View File

@ -0,0 +1,79 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
export class MigrationCleanup1752509043847 {
name = 'MigrationCleanup1752509043847'
async up(queryRunner) {
// 1745378064470-composite-note-index.js created a index ON "note" ("userId", "id" DESC) as IDX_724b311e6f883751f261ebe378 but should be named IDX_a6f649630f55af3888e5a42919
await queryRunner.query(`ALTER INDEX "IDX_724b311e6f883751f261ebe378" RENAME TO "IDX_a6f649630f55af3888e5a42919"`);
// 1713656541000-abuse-report-notification.js generated system_webhook with hand-written SQL with CURRENT_TIMESTAMP as the default value, but its representation in TypeORM is `now()`
// see https://github.com/typeorm/typeorm/blob/f351757a15b9d2bd9d4222c69dcfd2316f46b5d1/src/driver/postgres/PostgresDriver.ts#L1575
await queryRunner.query(`ALTER TABLE "system_webhook" ALTER COLUMN "updatedAt" SET DEFAULT now()`);
// 1702718871541-ffVisibility.js defined a enum type "user_profile_followersVisibility_enum" but it should be "user_profile_followersvisibility_enum" (lowercase 'v') in typeorm
await queryRunner.query(`ALTER TYPE "public"."user_profile_followersVisibility_enum" RENAME TO "user_profile_followersvisibility_enum"`);
// 1713656541000-abuse-report-notification.js generated abuse_report_notification_recipient with hand-written SQL with CURRENT_TIMESTAMP as the default value, but its representation in TypeORM is `now()`
await queryRunner.query(`ALTER TABLE "abuse_report_notification_recipient" ALTER COLUMN "updatedAt" SET DEFAULT now()`);
// 1690796169261-play-visibility.js added visibility column to flash table but it forgot to set NOT NULL constraint
await queryRunner.query(`ALTER TABLE "flash" ALTER COLUMN "visibility" SET NOT NULL`);
// 1736686850345-createNoteDraft.js created note_draft with hand-written SQL but several types and comments are not correctly defined
await queryRunner.query(`CREATE TYPE "public"."note_draft_visibility_enum" AS ENUM('public', 'home', 'followers', 'specified')`);
await queryRunner.query(`ALTER TABLE "note_draft" ALTER COLUMN "id" SET DATA TYPE character varying(32)`);
await queryRunner.query(`ALTER TABLE "note_draft" ALTER COLUMN "replyId" SET DATA TYPE character varying(32)`);
await queryRunner.query(`ALTER TABLE "note_draft" ALTER COLUMN "renoteId" SET DATA TYPE character varying(32)`);
await queryRunner.query(`ALTER TABLE "note_draft" ALTER COLUMN "userId" SET DATA TYPE character varying(32)`);
await queryRunner.query(`ALTER TABLE "note_draft" ALTER COLUMN "channelId" SET DATA TYPE character varying(32)`);
await queryRunner.query(`ALTER TABLE "note_draft" ALTER COLUMN "fileIds" SET DATA TYPE character varying(32) array`);
await queryRunner.query(`ALTER TABLE "note_draft" ALTER COLUMN "visibleUserIds" SET DATA TYPE character varying(32) array`);
await queryRunner.query(`ALTER TABLE "note_draft" ALTER COLUMN "visibility" SET DATA TYPE "public"."note_draft_visibility_enum" USING visibility::note_draft_visibility_enum`);
await queryRunner.query(`COMMENT ON COLUMN "note_draft"."replyId" IS 'The ID of reply target.'`);
await queryRunner.query(`COMMENT ON COLUMN "note_draft"."renoteId" IS 'The ID of renote target.'`);
await queryRunner.query(`COMMENT ON COLUMN "note_draft"."userId" IS 'The ID of author.'`);
await queryRunner.query(`COMMENT ON COLUMN "note_draft"."channelId" IS 'The ID of source channel.'`);
await queryRunner.query(`ALTER TABLE "note_draft" ALTER COLUMN "fileIds" SET NOT NULL`);
await queryRunner.query(`ALTER TABLE "note_draft" ALTER COLUMN "hasPoll" SET NOT NULL`);
await queryRunner.query(`ALTER TABLE "note_draft" ALTER COLUMN "pollChoices" SET NOT NULL`);
await queryRunner.query(`ALTER TABLE "note_draft" ALTER COLUMN "pollMultiple" SET NOT NULL`);
await queryRunner.query(`ALTER TABLE "note_draft" ALTER COLUMN "localOnly" SET NOT NULL`);
await queryRunner.query(`ALTER TABLE "note_draft" ALTER COLUMN "visibleUserIds" SET NOT NULL`);
}
async down(queryRunner) {
await queryRunner.query(`ALTER TABLE "note_draft" ALTER COLUMN "visibleUserIds" DROP NOT NULL`);
await queryRunner.query(`ALTER TABLE "note_draft" ALTER COLUMN "localOnly" DROP NOT NULL`);
await queryRunner.query(`ALTER TABLE "note_draft" ALTER COLUMN "pollMultiple" DROP NOT NULL`);
await queryRunner.query(`ALTER TABLE "note_draft" ALTER COLUMN "pollChoices" DROP NOT NULL`);
await queryRunner.query(`ALTER TABLE "note_draft" ALTER COLUMN "hasPoll" DROP NOT NULL`);
await queryRunner.query(`ALTER TABLE "note_draft" ALTER COLUMN "fileIds" DROP NOT NULL`);
await queryRunner.query(`COMMENT ON COLUMN "note_draft"."channelId" IS NULL`);
await queryRunner.query(`COMMENT ON COLUMN "note_draft"."userId" IS 'The ID of author.'`);
await queryRunner.query(`COMMENT ON COLUMN "note_draft"."renoteId" IS NULL`);
await queryRunner.query(`COMMENT ON COLUMN "note_draft"."replyId" IS NULL`);
await queryRunner.query(`ALTER TABLE "note_draft" ALTER COLUMN "visibility" SET DATA TYPE varchar`);
await queryRunner.query(`ALTER TABLE "note_draft" ALTER COLUMN "visibleUserIds" SET DATA TYPE varchar[]`);
await queryRunner.query(`ALTER TABLE "note_draft" ALTER COLUMN "fileIds" SET DATA TYPE varchar[]`);
await queryRunner.query(`ALTER TABLE "note_draft" ALTER COLUMN "channelId" SET DATA TYPE varchar`);
await queryRunner.query(`ALTER TABLE "note_draft" ALTER COLUMN "userId" SET DATA TYPE varchar`);
await queryRunner.query(`ALTER TABLE "note_draft" ALTER COLUMN "renoteId" SET DATA TYPE varchar`);
await queryRunner.query(`ALTER TABLE "note_draft" ALTER COLUMN "replyId" SET DATA TYPE varchar`);
await queryRunner.query(`ALTER TABLE "note_draft" ALTER COLUMN "id" SET DATA TYPE varchar`);
await queryRunner.query(`DROP TYPE "public"."note_draft_visibility_enum"`);
await queryRunner.query(`ALTER TABLE "flash" ALTER COLUMN "visibility" DROP NOT NULL`);
await queryRunner.query(`ALTER TABLE "abuse_report_notification_recipient" ALTER COLUMN "updatedAt" SET DEFAULT CURRENT_TIMESTAMP`);
await queryRunner.query(`ALTER TYPE "public"."user_profile_followersvisibility_enum" RENAME TO "user_profile_followersVisibility_enum"`);
await queryRunner.query(`ALTER TABLE "system_webhook" ALTER COLUMN "updatedAt" SET DEFAULT CURRENT_TIMESTAMP`);
await queryRunner.query(`ALTER INDEX "IDX_a6f649630f55af3888e5a42919" RENAME TO "IDX_724b311e6f883751f261ebe378"`);
}
}

View File

@ -33,6 +33,7 @@
"test:fed": "pnpm jest:fed", "test:fed": "pnpm jest:fed",
"test-and-coverage": "pnpm jest-and-coverage", "test-and-coverage": "pnpm jest-and-coverage",
"test-and-coverage:e2e": "pnpm build && pnpm build:test && pnpm jest-and-coverage:e2e", "test-and-coverage:e2e": "pnpm build && pnpm build:test && pnpm jest-and-coverage:e2e",
"check-migrations": "node scripts/check_migrations_clean.js",
"generate-api-json": "node ./scripts/generate_api_json.js" "generate-api-json": "node ./scripts/generate_api_json.js"
}, },
"optionalDependencies": { "optionalDependencies": {

View File

@ -0,0 +1,26 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
// This script checks if the database migrations has been generated correctly.
import dataSource from '../ormconfig.js';
await dataSource.initialize();
const sqlInMemory = await dataSource.driver.createSchemaBuilder().log();
if (sqlInMemory.upQueries.length > 0 || sqlInMemory.downQueries.length > 0) {
console.error('There are several pending migrations. Please make sure you have generated the migrations correctly, or configured entities class correctly.');
for (const query of sqlInMemory.upQueries) {
console.error(`- ${query.query}`);
}
for (const query of sqlInMemory.downQueries) {
console.error(`- ${query.query}`);
}
process.exit(1);
} else {
console.log('All migrations are clean.');
process.exit(0);
}

View File

@ -22,7 +22,7 @@ export class MiAbuseReportNotificationRecipient {
/** /**
* . * .
*/ */
@Index() @Index('IDX_abuse_report_notification_recipient_isActive')
@Column('boolean', { @Column('boolean', {
default: true, default: true,
}) })
@ -47,7 +47,7 @@ export class MiAbuseReportNotificationRecipient {
/** /**
* . * .
*/ */
@Index() @Index('IDX_abuse_report_notification_recipient_method')
@Column('varchar', { @Column('varchar', {
length: 64, length: 64,
}) })
@ -56,10 +56,11 @@ export class MiAbuseReportNotificationRecipient {
/** /**
* ID. * ID.
*/ */
@Index() @Index('IDX_abuse_report_notification_recipient_userId')
@Column({ @Column({
...id(), ...id(),
nullable: true, nullable: true,
default: null,
}) })
public userId: MiUser['id'] | 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' }) @JoinColumn({ name: 'userId', referencedColumnName: 'userId', foreignKeyConstraintName: 'FK_abuse_report_notification_recipient_userId2' })
public userProfile: MiUserProfile | null; public userProfile: MiUserProfile | null;
/** /**
* WebhookId. * WebhookId.
*/ */
@Index() @Index('IDX_abuse_report_notification_recipient_systemWebhookId')
@Column({ @Column({
...id(), ...id(),
nullable: true, nullable: true,
default: null,
}) })
public systemWebhookId: string | null; public systemWebhookId: string | null;
@ -95,6 +99,6 @@ export class MiAbuseReportNotificationRecipient {
@ManyToOne(type => MiSystemWebhook, { @ManyToOne(type => MiSystemWebhook, {
onDelete: 'CASCADE', onDelete: 'CASCADE',
}) })
@JoinColumn() @JoinColumn({ name: 'systemWebhookId', referencedColumnName: 'id', foreignKeyConstraintName: 'FK_abuse_report_notification_recipient_systemWebhookId' })
public systemWebhook: MiSystemWebhook | null; public systemWebhook: MiSystemWebhook | null;
} }

View File

@ -8,6 +8,7 @@ import { id } from './util/id.js';
@Entity('emoji') @Entity('emoji')
@Index(['name', 'host'], { unique: true }) @Index(['name', 'host'], { unique: true })
@Index('IDX_EMOJI_ROLE_IDS', { synchronize: false }) // GIN for roleIdsThatCanBeUsedThisEmojiAsReaction in production
export class MiEmoji { export class MiEmoji {
@PrimaryColumn(id()) @PrimaryColumn(id())
public id: string; public id: string;
@ -32,6 +33,7 @@ export class MiEmoji {
@Column('varchar', { @Column('varchar', {
length: 128, nullable: true, length: 128, nullable: true,
}) })
@Index('IDX_EMOJI_CATEGORY')
public category: string | null; public category: string | null;
@Column('varchar', { @Column('varchar', {

View File

@ -59,7 +59,7 @@ export class MiMeta {
public maintainerEmail: string | null; public maintainerEmail: string | null;
@Column('boolean', { @Column('boolean', {
default: false, default: true,
}) })
public disableRegistration: boolean; public disableRegistration: boolean;
@ -570,7 +570,7 @@ export class MiMeta {
public bannedEmailDomains: string[]; public bannedEmailDomains: string[];
@Column('varchar', { @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[]; public preservedUsernames: string[];
@ -635,7 +635,7 @@ export class MiMeta {
public urlPreviewMaximumContentLength: number; public urlPreviewMaximumContentLength: number;
@Column('boolean', { @Column('boolean', {
default: true, default: false,
}) })
public urlPreviewRequireContentLength: boolean; public urlPreviewRequireContentLength: boolean;
@ -648,6 +648,7 @@ export class MiMeta {
@Column('varchar', { @Column('varchar', {
length: 1024, length: 1024,
nullable: true, nullable: true,
default: null,
}) })
public urlPreviewUserAgent: string | null; public urlPreviewUserAgent: string | null;

View File

@ -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 // 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. // 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, // 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') @Entity('note')
export class MiNote { export class MiNote {
@PrimaryColumn(id()) @PrimaryColumn(id())

View File

@ -12,11 +12,13 @@ import { MiNote } from './Note.js';
import type { MiDriveFile } from './DriveFile.js'; import type { MiDriveFile } from './DriveFile.js';
@Entity('note_draft') @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 { export class MiNoteDraft {
@PrimaryColumn(id()) @PrimaryColumn(id())
public id: string; public id: string;
@Index() @Index('IDX_NOTE_DRAFT_REPLY_ID')
@Column({ @Column({
...id(), ...id(),
nullable: true, nullable: true,
@ -31,7 +33,7 @@ export class MiNoteDraft {
@JoinColumn() @JoinColumn()
public reply: MiNote | null; public reply: MiNote | null;
@Index() @Index('IDX_NOTE_DRAFT_RENOTE_ID')
@Column({ @Column({
...id(), ...id(),
nullable: true, nullable: true,
@ -57,7 +59,7 @@ export class MiNoteDraft {
}) })
public cw: string | null; public cw: string | null;
@Index() @Index('IDX_NOTE_DRAFT_USER_ID')
@Column({ @Column({
...id(), ...id(),
comment: 'The ID of author.', comment: 'The ID of author.',
@ -108,7 +110,7 @@ export class MiNoteDraft {
}) })
public hashtag: string | null; public hashtag: string | null;
@Index() @Index('IDX_NOTE_DRAFT_CHANNEL_ID')
@Column({ @Column({
...id(), ...id(),
nullable: true, nullable: true,

View File

@ -29,7 +29,7 @@ export class MiUserProfile {
}) })
public location: string | null; public location: string | null;
@Index() // Note: There's index named IDX_de22cd2b445eee31ae51cdbe99 for SUBSTR("birthday", 6, 5)
@Column('char', { @Column('char', {
length: 10, nullable: true, length: 10, nullable: true,
comment: 'The birthday (YYYY-MM-DD) of the User.', comment: 'The birthday (YYYY-MM-DD) of the User.',

View File

@ -129,7 +129,8 @@ export class SignupApiService {
let ticket: MiRegistrationTicket | null = null; let ticket: MiRegistrationTicket | null = null;
if (this.meta.disableRegistration) { // テスト時はこの機構は障害となるため無効にする
if (process.env.NODE_ENV !== 'test' && this.meta.disableRegistration) {
if (invitationCode == null || typeof invitationCode !== 'string') { if (invitationCode == null || typeof invitationCode !== 'string') {
reply.code(400); reply.code(400);
return; return;

View File

@ -74,6 +74,10 @@ services:
source: ../../../pnpm-workspace.yaml source: ../../../pnpm-workspace.yaml
target: /misskey/pnpm-workspace.yaml target: /misskey/pnpm-workspace.yaml
read_only: true read_only: true
- type: bind
source: ../../../patches
target: /misskey/patches
read_only: true
- type: bind - type: bind
source: ./certificates/rootCA.crt source: ./certificates/rootCA.crt
target: /usr/local/share/ca-certificates/rootCA.crt target: /usr/local/share/ca-certificates/rootCA.crt

View File

@ -74,6 +74,10 @@ services:
source: ../../../pnpm-workspace.yaml source: ../../../pnpm-workspace.yaml
target: /misskey/pnpm-workspace.yaml target: /misskey/pnpm-workspace.yaml
read_only: true read_only: true
- type: bind
source: ../../../patches
target: /misskey/patches
read_only: true
- type: bind - type: bind
source: ./certificates/rootCA.crt source: ./certificates/rootCA.crt
target: /usr/local/share/ca-certificates/rootCA.crt target: /usr/local/share/ca-certificates/rootCA.crt
@ -118,6 +122,10 @@ services:
source: ../../../pnpm-workspace.yaml source: ../../../pnpm-workspace.yaml
target: /misskey/pnpm-workspace.yaml target: /misskey/pnpm-workspace.yaml
read_only: true read_only: true
- type: bind
source: ../../../patches
target: /misskey/patches
read_only: true
working_dir: /misskey working_dir: /misskey
command: > command: >
bash -c " bash -c "

View File

@ -10,7 +10,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</div> </div>
<div class="_spacer" style="--MI_SPACER-min: 20px; --MI_SPACER-max: 32px;"> <div class="_spacer" style="--MI_SPACER-min: 20px; --MI_SPACER-max: 32px;">
<form class="_gaps_m" autocomplete="new-password" @submit.prevent="onSubmit"> <form class="_gaps_m" autocomplete="new-password" @submit.prevent="onSubmit">
<MkInput v-if="instance.disableRegistration" v-model="invitationCode" type="text" :spellcheck="false" required> <MkInput v-if="instance.disableRegistration" v-model="invitationCode" type="text" :spellcheck="false" required data-cy-signup-invitation-code>
<template #label>{{ i18n.ts.invitationCode }}</template> <template #label>{{ i18n.ts.invitationCode }}</template>
<template #prefix><i class="ti ti-key"></i></template> <template #prefix><i class="ti ti-key"></i></template>
</MkInput> </MkInput>
@ -138,6 +138,7 @@ const shouldDisableSubmitting = computed((): boolean => {
instance.enableTurnstile && !turnstileResponse.value || instance.enableTurnstile && !turnstileResponse.value ||
instance.enableTestcaptcha && !testcaptchaResponse.value || instance.enableTestcaptcha && !testcaptchaResponse.value ||
instance.emailRequiredForSignup && emailState.value !== 'ok' || instance.emailRequiredForSignup && emailState.value !== 'ok' ||
instance.disableRegistration && invitationCode.value === '' ||
usernameState.value !== 'ok' || usernameState.value !== 'ok' ||
passwordRetypeState.value !== 'match'; passwordRetypeState.value !== 'match';
}); });

17
patches/typeorm.patch Normal file
View File

@ -0,0 +1,17 @@
diff --git a/driver/postgres/PostgresDriver.js b/driver/postgres/PostgresDriver.js
index 278f29c1f3deec4939bb4ed90e6edae167f704e0..9a84c3098dda915d6c33e24d925a8fa09af9095e 100644
--- a/driver/postgres/PostgresDriver.js
+++ b/driver/postgres/PostgresDriver.js
@@ -785,10 +785,10 @@ class PostgresDriver {
const tableColumnDefault = typeof tableColumn.default === "string"
? JSON.parse(tableColumn.default.substring(1, tableColumn.default.length - 1))
: tableColumn.default;
- return OrmUtils_1.OrmUtils.deepCompare(columnMetadata.default, tableColumnDefault);
+ return OrmUtils_1.OrmUtils.deepCompare(columnMetadata.default, tableColumnDefault ?? null);
}
const columnDefault = this.lowerDefaultValueIfNecessary(this.normalizeDefault(columnMetadata));
- return columnDefault === tableColumn.default;
+ return columnDefault === tableColumn.default || columnDefault === undefined && tableColumn.default.toLowerCase() === 'null';
}
/**
* Normalizes "isUnique" value of the column.

View File

@ -9,6 +9,11 @@ overrides:
lodash: 4.17.21 lodash: 4.17.21
'@aiscript-dev/aiscript-languageserver': '-' '@aiscript-dev/aiscript-languageserver': '-'
patchedDependencies:
typeorm:
hash: 2677b97a423e157945c154e64183d3ae2eb44dfa9cb0e5ce731a7612f507bb56
path: patches/typeorm.patch
importers: importers:
.: .:
@ -422,7 +427,7 @@ importers:
version: 4.2.0 version: 4.2.0
typeorm: typeorm:
specifier: 0.3.24 specifier: 0.3.24
version: 0.3.24(ioredis@5.6.1)(pg@8.16.0)(reflect-metadata@0.2.2) version: 0.3.24(patch_hash=2677b97a423e157945c154e64183d3ae2eb44dfa9cb0e5ce731a7612f507bb56)(ioredis@5.6.1)(pg@8.16.0)(reflect-metadata@0.2.2)
typescript: typescript:
specifier: 5.8.3 specifier: 5.8.3
version: 5.8.3 version: 5.8.3
@ -21835,7 +21840,7 @@ snapshots:
typedarray@0.0.6: {} typedarray@0.0.6: {}
typeorm@0.3.24(ioredis@5.6.1)(pg@8.16.0)(reflect-metadata@0.2.2): typeorm@0.3.24(patch_hash=2677b97a423e157945c154e64183d3ae2eb44dfa9cb0e5ce731a7612f507bb56)(ioredis@5.6.1)(pg@8.16.0)(reflect-metadata@0.2.2):
dependencies: dependencies:
'@sqltools/formatter': 1.2.5 '@sqltools/formatter': 1.2.5
ansis: 3.17.0 ansis: 3.17.0