mirror of
https://github.com/chenasraf/nextcloud-forum.git
synced 2026-05-17 17:28:02 +00:00
fix: forum users tables migrations
This commit is contained in:
@@ -31,7 +31,6 @@ use OCP\AppFramework\Db\Entity;
|
||||
* @method void setUpdatedAt(int $updatedAt)
|
||||
*/
|
||||
class ForumUser extends Entity implements JsonSerializable {
|
||||
public $id;
|
||||
protected string $userId = '';
|
||||
protected int $postCount = 0;
|
||||
protected int $threadCount = 0;
|
||||
|
||||
@@ -125,7 +125,8 @@ class SeedHelper {
|
||||
|
||||
/**
|
||||
* Create the forum_users table from scratch
|
||||
* This mirrors the schema from Version1 + Version8 migrations
|
||||
* This mirrors the final schema from Version1 + Version2 + Version8 migrations
|
||||
* (id as primary key, user_id as unique, includes signature column)
|
||||
*/
|
||||
private static function createForumUsersTable(\OCP\IDBConnection $db): void {
|
||||
$platform = $db->getDatabasePlatform();
|
||||
@@ -138,6 +139,7 @@ class SeedHelper {
|
||||
// MySQL and MariaDB both extend MySQLPlatform
|
||||
$db->executeStatement("
|
||||
CREATE TABLE `{$tableName}` (
|
||||
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
`user_id` VARCHAR(64) NOT NULL,
|
||||
`post_count` INT UNSIGNED NOT NULL DEFAULT 0,
|
||||
`thread_count` INT UNSIGNED NOT NULL DEFAULT 0,
|
||||
@@ -146,14 +148,17 @@ class SeedHelper {
|
||||
`signature` TEXT DEFAULT NULL,
|
||||
`created_at` INT UNSIGNED NOT NULL,
|
||||
`updated_at` INT UNSIGNED NOT NULL,
|
||||
PRIMARY KEY (`user_id`),
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE INDEX `forum_users_user_id_uniq` (`user_id`),
|
||||
INDEX `forum_users_post_count_idx` (`post_count`),
|
||||
INDEX `forum_users_thread_count_idx` (`thread_count`),
|
||||
INDEX `forum_users_deleted_at_idx` (`deleted_at`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin
|
||||
");
|
||||
} elseif ($platform instanceof \Doctrine\DBAL\Platforms\PostgreSQLPlatform) {
|
||||
$db->executeStatement("
|
||||
CREATE TABLE \"{$tableName}\" (
|
||||
\"id\" BIGSERIAL PRIMARY KEY,
|
||||
\"user_id\" VARCHAR(64) NOT NULL,
|
||||
\"post_count\" INTEGER NOT NULL DEFAULT 0,
|
||||
\"thread_count\" INTEGER NOT NULL DEFAULT 0,
|
||||
@@ -161,16 +166,18 @@ class SeedHelper {
|
||||
\"deleted_at\" INTEGER DEFAULT NULL,
|
||||
\"signature\" TEXT DEFAULT NULL,
|
||||
\"created_at\" INTEGER NOT NULL,
|
||||
\"updated_at\" INTEGER NOT NULL,
|
||||
PRIMARY KEY (\"user_id\")
|
||||
\"updated_at\" INTEGER NOT NULL
|
||||
)
|
||||
");
|
||||
$db->executeStatement("CREATE UNIQUE INDEX \"forum_users_user_id_uniq\" ON \"{$tableName}\" (\"user_id\")");
|
||||
$db->executeStatement("CREATE INDEX \"forum_users_post_count_idx\" ON \"{$tableName}\" (\"post_count\")");
|
||||
$db->executeStatement("CREATE INDEX \"forum_users_thread_count_idx\" ON \"{$tableName}\" (\"thread_count\")");
|
||||
$db->executeStatement("CREATE INDEX \"forum_users_deleted_at_idx\" ON \"{$tableName}\" (\"deleted_at\")");
|
||||
} else {
|
||||
// SQLite (and any other platform as fallback)
|
||||
$db->executeStatement("
|
||||
CREATE TABLE \"{$tableName}\" (
|
||||
\"id\" INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
\"user_id\" VARCHAR(64) NOT NULL,
|
||||
\"post_count\" INTEGER NOT NULL DEFAULT 0,
|
||||
\"thread_count\" INTEGER NOT NULL DEFAULT 0,
|
||||
@@ -178,11 +185,12 @@ class SeedHelper {
|
||||
\"deleted_at\" INTEGER DEFAULT NULL,
|
||||
\"signature\" TEXT DEFAULT NULL,
|
||||
\"created_at\" INTEGER NOT NULL,
|
||||
\"updated_at\" INTEGER NOT NULL,
|
||||
PRIMARY KEY (\"user_id\")
|
||||
\"updated_at\" INTEGER NOT NULL
|
||||
)
|
||||
");
|
||||
$db->executeStatement("CREATE UNIQUE INDEX \"forum_users_user_id_uniq\" ON \"{$tableName}\" (\"user_id\")");
|
||||
$db->executeStatement("CREATE INDEX \"forum_users_post_count_idx\" ON \"{$tableName}\" (\"post_count\")");
|
||||
$db->executeStatement("CREATE INDEX \"forum_users_thread_count_idx\" ON \"{$tableName}\" (\"thread_count\")");
|
||||
$db->executeStatement("CREATE INDEX \"forum_users_deleted_at_idx\" ON \"{$tableName}\" (\"deleted_at\")");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,12 +85,30 @@ class Version1Date20251106004226 extends SimpleMigrationStep {
|
||||
$table->addIndex(['name'], 'forum_roles_name_idx');
|
||||
}
|
||||
|
||||
/**
|
||||
* Create forum_users table (formerly forum_user_stats)
|
||||
* Note: On fresh installs, this creates forum_users directly with the final schema.
|
||||
* For progressive installs where forum_user_stats already exists,
|
||||
* SeedHelper::ensureForumUsersTable() handles the rename.
|
||||
*
|
||||
* The table structure matches what Version2 transforms it to:
|
||||
* - id: auto-increment primary key
|
||||
* - user_id: unique string
|
||||
* - signature: added in Version8
|
||||
*/
|
||||
private function createUserStatsTable(ISchemaWrapper $schema): void {
|
||||
if ($schema->hasTable('forum_user_stats')) {
|
||||
// Skip if either table already exists (handles both fresh and progressive installs)
|
||||
if ($schema->hasTable('forum_users') || $schema->hasTable('forum_user_stats')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$table = $schema->createTable('forum_user_stats');
|
||||
// Create forum_users directly with the final schema (matching Version2's transformation)
|
||||
$table = $schema->createTable('forum_users');
|
||||
$table->addColumn('id', 'bigint', [
|
||||
'autoincrement' => true,
|
||||
'notnull' => true,
|
||||
'unsigned' => true,
|
||||
]);
|
||||
$table->addColumn('user_id', 'string', [
|
||||
'notnull' => true,
|
||||
'length' => 64,
|
||||
@@ -115,6 +133,10 @@ class Version1Date20251106004226 extends SimpleMigrationStep {
|
||||
'unsigned' => true,
|
||||
'default' => null,
|
||||
]);
|
||||
$table->addColumn('signature', 'text', [
|
||||
'notnull' => false,
|
||||
'default' => null,
|
||||
]);
|
||||
$table->addColumn('created_at', 'integer', [
|
||||
'notnull' => true,
|
||||
'unsigned' => true,
|
||||
@@ -123,9 +145,11 @@ class Version1Date20251106004226 extends SimpleMigrationStep {
|
||||
'notnull' => true,
|
||||
'unsigned' => true,
|
||||
]);
|
||||
$table->setPrimaryKey(['user_id']);
|
||||
$table->addIndex(['post_count'], 'user_stats_post_count_idx');
|
||||
$table->addIndex(['deleted_at'], 'user_stats_deleted_at_idx');
|
||||
$table->setPrimaryKey(['id']);
|
||||
$table->addUniqueIndex(['user_id'], 'forum_users_user_id_uniq');
|
||||
$table->addIndex(['post_count'], 'forum_users_post_count_idx');
|
||||
$table->addIndex(['thread_count'], 'forum_users_thread_count_idx');
|
||||
$table->addIndex(['deleted_at'], 'forum_users_deleted_at_idx');
|
||||
}
|
||||
|
||||
private function createForumUserRolesTable(ISchemaWrapper $schema): void {
|
||||
|
||||
@@ -67,18 +67,38 @@ class Version2Date20251114222614 extends SimpleMigrationStep {
|
||||
$table->addUniqueIndex(['user_id', 'thread_id'], 'thread_subs_uniq_idx');
|
||||
}
|
||||
|
||||
/**
|
||||
* Fix forum_user_stats or forum_users table structure
|
||||
* Handles both old table name (progressive installs) and new table name (fresh installs)
|
||||
*/
|
||||
private function fixForumUserStatsTable(ISchemaWrapper $schema): void {
|
||||
if (!$schema->hasTable('forum_user_stats')) {
|
||||
// Determine which table exists (handles both fresh and progressive installs)
|
||||
$tableName = null;
|
||||
if ($schema->hasTable('forum_user_stats')) {
|
||||
$tableName = 'forum_user_stats';
|
||||
} elseif ($schema->hasTable('forum_users')) {
|
||||
$tableName = 'forum_users';
|
||||
}
|
||||
|
||||
if ($tableName === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
$table = $schema->getTable('forum_user_stats');
|
||||
$table = $schema->getTable($tableName);
|
||||
|
||||
// Check if already fixed (has id column)
|
||||
// Note: On fresh installs, forum_users uses user_id as primary key (no id column needed)
|
||||
// This fix is only needed for progressive installs with old forum_user_stats structure
|
||||
if ($table->hasColumn('id')) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Only add id column to forum_user_stats (old structure)
|
||||
// forum_users created in Version1 uses user_id as primary key and doesn't need this fix
|
||||
if ($tableName !== 'forum_user_stats') {
|
||||
return;
|
||||
}
|
||||
|
||||
// Add id column as auto-increment
|
||||
$table->addColumn('id', 'bigint', [
|
||||
'autoincrement' => true,
|
||||
@@ -123,8 +143,7 @@ class Version2Date20251114222614 extends SimpleMigrationStep {
|
||||
}
|
||||
|
||||
/**
|
||||
* Rebuild user stats using the old table name (forum_user_stats)
|
||||
* This is needed because Version8 hasn't renamed the table yet
|
||||
* Rebuild user stats - handles both old (forum_user_stats) and new (forum_users) table names
|
||||
*/
|
||||
private function rebuildAllUserStatsLegacy(): array {
|
||||
// Get all user IDs from Nextcloud
|
||||
@@ -133,11 +152,22 @@ class Version2Date20251114222614 extends SimpleMigrationStep {
|
||||
$users[] = $user->getUID();
|
||||
});
|
||||
|
||||
// Determine which table to use
|
||||
$tableName = $this->getUserStatsTableName();
|
||||
if ($tableName === null) {
|
||||
// No table exists yet - this shouldn't happen but handle gracefully
|
||||
return [
|
||||
'users' => count($users),
|
||||
'updated' => 0,
|
||||
'created' => 0,
|
||||
];
|
||||
}
|
||||
|
||||
$updated = 0;
|
||||
$created = 0;
|
||||
|
||||
foreach ($users as $userId) {
|
||||
$wasCreated = $this->rebuildUserStatsLegacy($userId);
|
||||
$wasCreated = $this->rebuildUserStatsLegacy($userId, $tableName);
|
||||
if ($wasCreated) {
|
||||
$created++;
|
||||
} else {
|
||||
@@ -153,9 +183,36 @@ class Version2Date20251114222614 extends SimpleMigrationStep {
|
||||
}
|
||||
|
||||
/**
|
||||
* Rebuild stats for a single user using the old table name
|
||||
* Get the user stats table name (handles both old and new names)
|
||||
*/
|
||||
private function rebuildUserStatsLegacy(string $userId): bool {
|
||||
private function getUserStatsTableName(): ?string {
|
||||
// Check forum_users first (new name, for fresh installs)
|
||||
// Then check forum_user_stats (old name, for progressive installs)
|
||||
try {
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
$qb->select('user_id')->from('forum_users')->setMaxResults(1);
|
||||
$qb->executeQuery()->closeCursor();
|
||||
return 'forum_users';
|
||||
} catch (\Exception $e) {
|
||||
// Table doesn't exist, try old name
|
||||
}
|
||||
|
||||
try {
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
$qb->select('user_id')->from('forum_user_stats')->setMaxResults(1);
|
||||
$qb->executeQuery()->closeCursor();
|
||||
return 'forum_user_stats';
|
||||
} catch (\Exception $e) {
|
||||
// Neither table exists
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Rebuild stats for a single user
|
||||
*/
|
||||
private function rebuildUserStatsLegacy(string $userId, string $tableName): bool {
|
||||
// Count non-deleted threads created by this user
|
||||
$threadQb = $this->db->getQueryBuilder();
|
||||
$threadQb->select($threadQb->func()->count('*', 'count'))
|
||||
@@ -194,10 +251,10 @@ class Version2Date20251114222614 extends SimpleMigrationStep {
|
||||
$lastPostAt = $lastPostResult->fetchOne();
|
||||
$lastPostResult->closeCursor();
|
||||
|
||||
// Check if forum user record already exists (using OLD table name)
|
||||
// Check if forum user record already exists
|
||||
$checkQb = $this->db->getQueryBuilder();
|
||||
$checkQb->select('user_id')
|
||||
->from('forum_user_stats') // OLD table name!
|
||||
->from($tableName)
|
||||
->where($checkQb->expr()->eq('user_id', $checkQb->createNamedParameter($userId)));
|
||||
$checkResult = $checkQb->executeQuery();
|
||||
$exists = $checkResult->fetch();
|
||||
@@ -206,9 +263,9 @@ class Version2Date20251114222614 extends SimpleMigrationStep {
|
||||
$timestamp = time();
|
||||
|
||||
if ($exists) {
|
||||
// Update existing record (using OLD table name)
|
||||
// Update existing record
|
||||
$updateQb = $this->db->getQueryBuilder();
|
||||
$updateQb->update('forum_user_stats') // OLD table name!
|
||||
$updateQb->update($tableName)
|
||||
->set('thread_count', $updateQb->createNamedParameter($threadCount, IQueryBuilder::PARAM_INT))
|
||||
->set('post_count', $updateQb->createNamedParameter($postCount, IQueryBuilder::PARAM_INT))
|
||||
->set('updated_at', $updateQb->createNamedParameter($timestamp, IQueryBuilder::PARAM_INT))
|
||||
@@ -221,9 +278,9 @@ class Version2Date20251114222614 extends SimpleMigrationStep {
|
||||
$updateQb->executeStatement();
|
||||
return false;
|
||||
} else {
|
||||
// Create new record (using OLD table name)
|
||||
// Create new record
|
||||
$insertQb = $this->db->getQueryBuilder();
|
||||
$insertQb->insert('forum_user_stats') // OLD table name!
|
||||
$insertQb->insert($tableName)
|
||||
->values([
|
||||
'user_id' => $insertQb->createNamedParameter($userId),
|
||||
'thread_count' => $insertQb->createNamedParameter($threadCount, IQueryBuilder::PARAM_INT),
|
||||
@@ -239,7 +296,7 @@ class Version2Date20251114222614 extends SimpleMigrationStep {
|
||||
} catch (\Exception $e) {
|
||||
// If insert fails (race condition), try updating instead
|
||||
$updateQb = $this->db->getQueryBuilder();
|
||||
$updateQb->update('forum_user_stats') // OLD table name!
|
||||
$updateQb->update($tableName)
|
||||
->set('thread_count', $updateQb->createNamedParameter($threadCount, IQueryBuilder::PARAM_INT))
|
||||
->set('post_count', $updateQb->createNamedParameter($postCount, IQueryBuilder::PARAM_INT))
|
||||
->set('updated_at', $updateQb->createNamedParameter($timestamp, IQueryBuilder::PARAM_INT))
|
||||
|
||||
@@ -43,9 +43,18 @@ class Version8Date20251128000000 extends SimpleMigrationStep {
|
||||
}
|
||||
}
|
||||
|
||||
// Add signature column to user stats
|
||||
if ($schema->hasTable('forum_user_stats')) {
|
||||
$table = $schema->getTable('forum_user_stats');
|
||||
// Add signature column to forum_users table (handles both old and new table names)
|
||||
// On fresh installs: forum_users is created with signature column in Version1
|
||||
// On progressive installs: forum_user_stats may still exist and needs signature added
|
||||
$userTableName = null;
|
||||
if ($schema->hasTable('forum_users')) {
|
||||
$userTableName = 'forum_users';
|
||||
} elseif ($schema->hasTable('forum_user_stats')) {
|
||||
$userTableName = 'forum_user_stats';
|
||||
}
|
||||
|
||||
if ($userTableName !== null) {
|
||||
$table = $schema->getTable($userTableName);
|
||||
|
||||
if (!$table->hasColumn('signature')) {
|
||||
$table->addColumn('signature', 'text', [
|
||||
|
||||
Reference in New Issue
Block a user