Ship legacy migrate APIs, portfolio/blog old-id schema, and admin migrate/purge flows.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Alireza Hassani
2026-07-29 16:10:41 +03:30
co-authored by Cursor
parent 7244b70e90
commit 4598add88c
32 changed files with 4234 additions and 265 deletions
+71
View File
@@ -0,0 +1,71 @@
import {
Injectable,
OnModuleDestroy,
ServiceUnavailableException,
} from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import mysql, { Pool, PoolOptions, RowDataPacket } from 'mysql2/promise';
@Injectable()
export class LegacyMysqlService implements OnModuleDestroy {
private pool: Pool | null = null;
constructor(private readonly config: ConfigService) {}
async onModuleDestroy() {
if (this.pool) {
await this.pool.end();
this.pool = null;
}
}
isConfigured(): boolean {
return Boolean(
this.config.get<string>('OLD_MYSQL_HOST') &&
this.config.get<string>('OLD_MYSQL_USER') &&
this.config.get<string>('OLD_MYSQL_DATABASE'),
);
}
async query<T extends RowDataPacket[]>(
sql: string,
params: unknown[] = [],
): Promise<T> {
const pool = this.getPool();
try {
const [rows] = await pool.query<T>(sql, params);
return rows;
} catch (err) {
const message = err instanceof Error ? err.message : 'Unknown MySQL error';
throw new ServiceUnavailableException(
`Old CMS MySQL query failed: ${message}. Ensure the SSH tunnel is up and OLD_MYSQL_* is set.`,
);
}
}
private getPool(): Pool {
if (this.pool) {
return this.pool;
}
if (!this.isConfigured()) {
throw new ServiceUnavailableException(
'Old CMS MySQL is not configured. Set OLD_MYSQL_HOST, OLD_MYSQL_USER, OLD_MYSQL_DATABASE (and password/port).',
);
}
const options: PoolOptions = {
host: this.config.getOrThrow<string>('OLD_MYSQL_HOST'),
port: Number(this.config.get<string>('OLD_MYSQL_PORT', '3307')),
user: this.config.getOrThrow<string>('OLD_MYSQL_USER'),
password: this.config.get<string>('OLD_MYSQL_PASSWORD', ''),
database: this.config.getOrThrow<string>('OLD_MYSQL_DATABASE'),
waitForConnections: true,
connectionLimit: 4,
namedPlaceholders: false,
};
this.pool = mysql.createPool(options);
return this.pool;
}
}