chore(db): finish Prisma cutover to Drizzle Kit tooling
CI / check (push) Failing after 9s
CI / release (push) Skipped
CI / deploy (push) Skipped

Move CMS SQL to drizzle/migrations, drop prisma packages/schema, wire drizzle-kit scripts, and regenerate schema names from src/db/schema.ts.

Co-authored-by: Cursor <[email protected]>
This commit is contained in:
SimoandCursor committed 2026-08-01 15:02:21 +02:00
1 parent d8199ea1e4
commit ba82789166
47 files changed
+365 -2787

No files matched your search

View File
Whitespace-only changes.
+1
View File
@@ -0,0 +1 @@
Draft SQL from `pnpm db:generate` (drizzle-kit). Never apply these automatically — copy reviewed statements into drizzle/migrations/ as numbered CMS migrations, then `pnpm db:migrate`.
@@ -0,0 +1,10 @@
-- CMS-owned settings table. Idempotent: safe to re-run.
-- Mirrors AtomCMS's Laravel `website_settings` (key UNIQUE, value TEXT).
CREATE TABLE IF NOT EXISTS website_settings (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
`key` VARCHAR(255) NOT NULL,
value TEXT NOT NULL,
comment VARCHAR(255) NULL,
PRIMARY KEY (id),
UNIQUE KEY website_settings_key_unique (`key`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
+6
View File
@@ -0,0 +1,6 @@
-- Fortify-style 2FA columns on the emulator users table. Idempotent (MariaDB).
-- AtomCMS installs already have these; this only adds them when missing.
ALTER TABLE users
ADD COLUMN IF NOT EXISTS two_factor_secret TEXT NULL,
ADD COLUMN IF NOT EXISTS two_factor_recovery_codes TEXT NULL,
ADD COLUMN IF NOT EXISTS two_factor_confirmed_at TIMESTAMP NULL;
@@ -0,0 +1,7 @@
-- CMS password reset tokens (Laravel-compatible). Idempotent.
CREATE TABLE IF NOT EXISTS password_resets (
email VARCHAR(255) NOT NULL,
token VARCHAR(255) NOT NULL,
created_at TIMESTAMP NULL,
PRIMARY KEY (email)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
@@ -0,0 +1,440 @@
-- 0004_cms_feature_tables.sql
-- Idempotent CREATE TABLE for the CMS-owned (website-tier) feature tables
-- the AtomCMS-Next conversion needs but the live emulator DB lacks:
-- radio_*, game_*/challenges, staff_activities, alert_logs, email_templates,
-- social_accounts, website_article_comments/reactions. Generated from
-- `prisma migrate diff` (CREATE statements only; all ALTER/DROP excluded so
-- emulator-owned tables are never touched). Each statement is IF NOT EXISTS.
CREATE TABLE IF NOT EXISTS `website_article_reactions` (
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
`user_id` INTEGER UNSIGNED NOT NULL,
`article_id` BIGINT UNSIGNED NOT NULL,
`reaction` VARCHAR(50) NOT NULL,
`active` BOOLEAN NOT NULL DEFAULT true,
PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS `website_article_comments` (
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
`article_id` BIGINT UNSIGNED NOT NULL,
`user_id` INTEGER NOT NULL,
`comment` VARCHAR(255) NOT NULL,
`created_at` TIMESTAMP(0) NULL,
`updated_at` TIMESTAMP(0) NULL,
PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS `alert_logs` (
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
`type` VARCHAR(255) NOT NULL,
`severity` VARCHAR(255) NOT NULL,
`message` TEXT NOT NULL,
`context` JSON NULL,
`sent_via_email` BOOLEAN NOT NULL DEFAULT false,
`sent_via_discord` BOOLEAN NOT NULL DEFAULT false,
`is_read` BOOLEAN NOT NULL DEFAULT false,
`read_at` TIMESTAMP(0) NULL,
`created_at` TIMESTAMP(0) NULL,
`updated_at` TIMESTAMP(0) NULL,
PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS `radio_ranks` (
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
`name` VARCHAR(255) NOT NULL,
`description` VARCHAR(255) NULL,
`badge_code` VARCHAR(255) NULL,
`is_active` BOOLEAN NOT NULL DEFAULT true,
`created_at` TIMESTAMP(0) NULL,
`updated_at` TIMESTAMP(0) NULL,
PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS `radio_shouts` (
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
`user_id` BIGINT UNSIGNED NOT NULL,
`message` TEXT NOT NULL,
`created_at` TIMESTAMP(0) NULL,
`updated_at` TIMESTAMP(0) NULL,
PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS `radio_applications` (
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
`user_id` BIGINT UNSIGNED NOT NULL,
`real_name` VARCHAR(255) NOT NULL,
`age` INTEGER NOT NULL,
`availability` TEXT NOT NULL,
`experience` TEXT NULL,
`motivation` TEXT NOT NULL,
`music_style` TEXT NULL,
`discord_username` VARCHAR(255) NULL,
`rank_id` BIGINT UNSIGNED NULL,
`status` VARCHAR(255) NOT NULL DEFAULT 'pending',
`approved_by` BIGINT UNSIGNED NULL,
`approved_at` TIMESTAMP(0) NULL,
`rejected_by` BIGINT UNSIGNED NULL,
`rejected_at` TIMESTAMP(0) NULL,
`created_at` TIMESTAMP(0) NULL,
`updated_at` TIMESTAMP(0) NULL,
PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS `radio_schedules` (
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
`user_id` BIGINT UNSIGNED NOT NULL,
`day` VARCHAR(255) NOT NULL,
`start_time` TIME NOT NULL,
`end_time` TIME NOT NULL,
`show_name` VARCHAR(255) NULL,
`description` TEXT NULL,
`is_active` BOOLEAN NOT NULL DEFAULT true,
`created_at` TIMESTAMP(0) NULL,
`updated_at` TIMESTAMP(0) NULL,
PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS `radio_banners` (
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
`user_id` BIGINT UNSIGNED NOT NULL,
`image_path` VARCHAR(255) NOT NULL,
`title` VARCHAR(255) NULL,
`description` TEXT NULL,
`sort_order` INTEGER NOT NULL DEFAULT 0,
`is_active` BOOLEAN NOT NULL DEFAULT true,
`created_at` TIMESTAMP(0) NULL,
`updated_at` TIMESTAMP(0) NULL,
PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS `radio_history` (
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
`user_id` BIGINT UNSIGNED NOT NULL,
`show_name` VARCHAR(255) NULL,
`started_at` TIMESTAMP(0) NOT NULL,
`ended_at` TIMESTAMP(0) NULL,
`listeners_count` INTEGER NOT NULL DEFAULT 0,
`notes` TEXT NULL,
`created_at` TIMESTAMP(0) NULL,
`updated_at` TIMESTAMP(0) NULL,
PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS `radio_contests` (
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
`created_at` TIMESTAMP(0) NULL,
`updated_at` TIMESTAMP(0) NULL,
PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS `radio_giveaways` (
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
`created_at` TIMESTAMP(0) NULL,
`updated_at` TIMESTAMP(0) NULL,
PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS `radio_listener_points` (
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
`created_at` TIMESTAMP(0) NULL,
`updated_at` TIMESTAMP(0) NULL,
PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS `radio_song_requests` (
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
`created_at` TIMESTAMP(0) NULL,
`updated_at` TIMESTAMP(0) NULL,
PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS `radio_song_votes` (
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
`created_at` TIMESTAMP(0) NULL,
`updated_at` TIMESTAMP(0) NULL,
PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS `game_scores` (
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
`user_id` INTEGER UNSIGNED NOT NULL,
`game` VARCHAR(50) NOT NULL,
`score` INTEGER NOT NULL DEFAULT 0,
`created_at` TIMESTAMP(0) NULL,
`updated_at` TIMESTAMP(0) NULL,
PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS `game_rooms` (
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
`game` VARCHAR(50) NOT NULL,
`player1_id` INTEGER UNSIGNED NOT NULL,
`player2_id` INTEGER UNSIGNED NULL,
`state` JSON NULL,
`turn` INTEGER UNSIGNED NOT NULL DEFAULT 1,
`winner_id` INTEGER UNSIGNED NULL,
`status` VARCHAR(191) NOT NULL DEFAULT 'waiting',
`created_at` TIMESTAMP(0) NULL,
`updated_at` TIMESTAMP(0) NULL,
PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS `game_reward_claims` (
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
`user_id` BIGINT UNSIGNED NOT NULL,
`game` VARCHAR(50) NOT NULL,
`wins_required` INTEGER NOT NULL,
`created_at` TIMESTAMP(0) NULL,
`updated_at` TIMESTAMP(0) NULL,
UNIQUE INDEX `game_reward_claims_user_id_game_wins_required_key`(`user_id`, `game`, `wins_required`),
PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS `game_chat_messages` (
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
`room_id` BIGINT UNSIGNED NOT NULL,
`user_id` BIGINT UNSIGNED NOT NULL,
`message` TEXT NOT NULL,
`type` VARCHAR(191) NOT NULL DEFAULT 'chat',
`reactions` JSON NULL,
`created_at` TIMESTAMP(0) NOT NULL,
PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS `game_replays` (
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
`game` VARCHAR(191) NOT NULL,
`player1_id` BIGINT UNSIGNED NOT NULL,
`player2_id` BIGINT UNSIGNED NULL,
`game_data` JSON NOT NULL,
`moves` JSON NOT NULL,
`winner` VARCHAR(191) NULL,
`player1_score` INTEGER NOT NULL DEFAULT 0,
`player2_score` INTEGER NOT NULL DEFAULT 0,
`started_at` TIMESTAMP(0) NOT NULL,
`ended_at` TIMESTAMP(0) NULL,
`created_at` TIMESTAMP(0) NULL,
`updated_at` TIMESTAMP(0) NULL,
PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS `game_ranks` (
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
`name` VARCHAR(191) NOT NULL,
`slug` VARCHAR(191) NOT NULL,
`icon` VARCHAR(191) NOT NULL DEFAULT '⭐',
`color` VARCHAR(191) NOT NULL DEFAULT '#ffffff',
`min_points` INTEGER NOT NULL DEFAULT 0,
`max_points` INTEGER NULL,
`tier` INTEGER NOT NULL DEFAULT 1,
`created_at` TIMESTAMP(0) NULL,
`updated_at` TIMESTAMP(0) NULL,
UNIQUE INDEX `game_ranks_slug_key`(`slug`),
PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS `user_game_ranks` (
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
`user_id` BIGINT UNSIGNED NOT NULL,
`game` VARCHAR(191) NOT NULL,
`rank_id` BIGINT UNSIGNED NOT NULL,
`points` INTEGER NOT NULL DEFAULT 0,
`wins` INTEGER NOT NULL DEFAULT 0,
`games_played` INTEGER NOT NULL DEFAULT 0,
`win_streak` INTEGER NOT NULL DEFAULT 0,
`best_streak` INTEGER NOT NULL DEFAULT 0,
`last_played_at` TIMESTAMP(0) NULL,
`created_at` TIMESTAMP(0) NULL,
`updated_at` TIMESTAMP(0) NULL,
UNIQUE INDEX `user_game_ranks_user_id_game_key`(`user_id`, `game`),
PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS `game_invitations` (
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
`from_user_id` BIGINT UNSIGNED NOT NULL,
`to_user_id` BIGINT UNSIGNED NOT NULL,
`game` VARCHAR(255) NOT NULL,
`status` VARCHAR(255) NOT NULL DEFAULT 'pending',
`bet_amount` INTEGER NOT NULL DEFAULT 0,
`bet_currency` VARCHAR(255) NOT NULL DEFAULT 'credits',
`room_id` BIGINT UNSIGNED NULL,
`expires_at` TIMESTAMP(0) NULL,
`created_at` TIMESTAMP(0) NULL,
`updated_at` TIMESTAMP(0) NULL,
PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS `user_game_xp` (
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
`user_id` BIGINT UNSIGNED NOT NULL,
`game` VARCHAR(255) NOT NULL,
`xp` INTEGER NOT NULL DEFAULT 0,
`level` INTEGER NOT NULL DEFAULT 1,
`created_at` TIMESTAMP(0) NULL,
`updated_at` TIMESTAMP(0) NULL,
UNIQUE INDEX `user_game_xp_user_id_game_key`(`user_id`, `game`),
PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS `game_level_rewards` (
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
`game` VARCHAR(255) NOT NULL,
`level` INTEGER NOT NULL,
`reward_type` VARCHAR(255) NOT NULL,
`reward_currency` VARCHAR(255) NULL,
`reward_amount` INTEGER NOT NULL DEFAULT 0,
`claimed` BOOLEAN NOT NULL DEFAULT false,
`created_at` TIMESTAMP(0) NULL,
`updated_at` TIMESTAMP(0) NULL,
PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS `daily_challenges` (
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
`name` VARCHAR(255) NOT NULL,
`description` VARCHAR(255) NOT NULL,
`game` VARCHAR(255) NULL,
`type` VARCHAR(255) NOT NULL,
`target` INTEGER NOT NULL,
`reward_currency` VARCHAR(255) NOT NULL DEFAULT 'credits',
`reward_amount` INTEGER NOT NULL,
`date` DATE NOT NULL,
`created_at` TIMESTAMP(0) NULL,
`updated_at` TIMESTAMP(0) NULL,
PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS `user_challenge_progress` (
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
`user_id` BIGINT UNSIGNED NOT NULL,
`challenge_id` BIGINT UNSIGNED NOT NULL,
`progress` INTEGER NOT NULL DEFAULT 0,
`completed` BOOLEAN NOT NULL DEFAULT false,
`claimed` BOOLEAN NOT NULL DEFAULT false,
`completed_at` TIMESTAMP(0) NULL,
`created_at` TIMESTAMP(0) NULL,
`updated_at` TIMESTAMP(0) NULL,
UNIQUE INDEX `user_challenge_progress_user_id_challenge_id_key`(`user_id`, `challenge_id`),
PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS `staff_activities` (
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
`user_id` BIGINT UNSIGNED NOT NULL,
`action` VARCHAR(50) NOT NULL,
`target_type` VARCHAR(100) NULL,
`target_id` BIGINT UNSIGNED NULL,
`description` TEXT NOT NULL,
`ip_address` VARCHAR(45) NULL,
`metadata` JSON NULL,
`created_at` TIMESTAMP(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0),
`updated_at` TIMESTAMP(0) NULL,
PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS `email_templates` (
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
`name` VARCHAR(255) NOT NULL,
`subject` VARCHAR(255) NOT NULL,
`body` TEXT NOT NULL,
`variables` TEXT NULL,
`is_active` BOOLEAN NOT NULL DEFAULT true,
`created_at` TIMESTAMP(0) NULL,
`updated_at` TIMESTAMP(0) NULL,
UNIQUE INDEX `email_templates_name_key`(`name`),
PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS `social_accounts` (
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
`user_id` BIGINT UNSIGNED NOT NULL,
`provider` VARCHAR(255) NOT NULL,
`provider_id` VARCHAR(255) NOT NULL,
`avatar` VARCHAR(255) NULL,
`created_at` TIMESTAMP(0) NULL,
`updated_at` TIMESTAMP(0) NULL,
UNIQUE INDEX `social_accounts_provider_provider_id_key`(`provider`, `provider_id`),
PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS `radio_api_keys` (
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
`name` VARCHAR(255) NOT NULL,
`key` VARCHAR(64) NOT NULL,
`allowed_ips` VARCHAR(255) NULL,
`permissions` JSON NULL,
`rate_limit` INTEGER NOT NULL DEFAULT 300,
`last_used_at` TIMESTAMP(0) NULL,
`expires_at` TIMESTAMP(0) NULL,
`is_active` BOOLEAN NOT NULL DEFAULT true,
`created_at` TIMESTAMP(0) NULL,
`updated_at` TIMESTAMP(0) NULL,
UNIQUE INDEX `radio_api_keys_key_key`(`key`),
PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS `radio_song_plays` (
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
`title` VARCHAR(255) NOT NULL,
`artist` VARCHAR(255) NULL,
`album` VARCHAR(255) NULL,
`artwork_url` VARCHAR(255) NULL,
`duration` INTEGER UNSIGNED NULL,
`played_at` TIMESTAMP(0) NOT NULL,
`dj_id` BIGINT UNSIGNED NULL,
`metadata` JSON NULL,
`created_at` TIMESTAMP(0) NULL,
`updated_at` TIMESTAMP(0) NULL,
PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS `radio_auto_dj_playlist` (
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
`title` VARCHAR(255) NOT NULL,
`artist` VARCHAR(255) NULL,
`album` VARCHAR(255) NULL,
`artwork_url` VARCHAR(255) NULL,
`duration` INTEGER UNSIGNED NULL,
`play_count` INTEGER UNSIGNED NOT NULL DEFAULT 0,
`last_played_at` TIMESTAMP(0) NULL,
`is_active` BOOLEAN NOT NULL DEFAULT true,
`sort_order` INTEGER UNSIGNED NOT NULL DEFAULT 0,
`created_at` TIMESTAMP(0) NULL,
`updated_at` TIMESTAMP(0) NULL,
PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
@@ -0,0 +1,8 @@
-- radio_listener_points was created as a stub (id + timestamps only) but the
-- /radio/leaderboard query joins users on user_id and orders by points. Add the
-- real columns. Idempotent (MariaDB ADD COLUMN / INDEX IF NOT EXISTS).
ALTER TABLE radio_listener_points
ADD COLUMN IF NOT EXISTS user_id INT NOT NULL DEFAULT 0,
ADD COLUMN IF NOT EXISTS points INT NOT NULL DEFAULT 0;
CREATE INDEX IF NOT EXISTS radio_listener_points_points_idx ON radio_listener_points (points);
@@ -0,0 +1,13 @@
-- CMS-owned editable content boxes (AtomCMS WriteableBox). Rendered on the
-- public site and managed from housekeeping. Idempotent (MariaDB).
CREATE TABLE IF NOT EXISTS website_writeable_boxes (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
title VARCHAR(255) NOT NULL,
icon VARCHAR(255) NULL,
content TEXT NOT NULL,
position INT NOT NULL DEFAULT 0,
is_active TINYINT(1) NOT NULL DEFAULT 1,
created_at TIMESTAMP NULL,
updated_at TIMESTAMP NULL,
PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
+11
View File
@@ -0,0 +1,11 @@
-- Successful-login history (the AtomCMS "session logs" equivalent; our auth is
-- stateless JWT so there's no sessions table). Idempotent (MariaDB).
CREATE TABLE IF NOT EXISTS website_login_logs (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
user_id INT NOT NULL,
ip VARCHAR(45) NOT NULL,
user_agent VARCHAR(512) NULL,
created_at TIMESTAMP NULL,
PRIMARY KEY (id),
KEY website_login_logs_user_id_idx (user_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
@@ -0,0 +1,4 @@
-- 0008_add_bans_user_id_index.sql
-- Adds an index on bans.user_id to speed up ban lookups by user (used by
-- the access guard on every page request for logged-in users).
CREATE INDEX IF NOT EXISTS `bans_user_id_index` ON `bans` (`user_id`);
@@ -0,0 +1,29 @@
-- 0009_radio_contests_giveaways_columns.sql
-- Fills in the empty RadioContests and RadioGiveaways tables with meaningful
-- columns. These tables were created in migration 0004 with only id/timestamps.
ALTER TABLE `radio_contests`
ADD COLUMN IF NOT EXISTS `title` VARCHAR(255) NOT NULL AFTER `id`,
ADD COLUMN IF NOT EXISTS `description` TEXT NULL AFTER `title`,
ADD COLUMN IF NOT EXISTS `prize` VARCHAR(255) NULL AFTER `description`,
ADD COLUMN IF NOT EXISTS `start_date` TIMESTAMP(0) NULL AFTER `prize`,
ADD COLUMN IF NOT EXISTS `end_date` TIMESTAMP(0) NULL AFTER `start_date`,
ADD COLUMN IF NOT EXISTS `winner_id` BIGINT UNSIGNED NULL AFTER `end_date`,
ADD COLUMN IF NOT EXISTS `is_active` BOOLEAN NOT NULL DEFAULT true AFTER `winner_id`,
ADD COLUMN IF NOT EXISTS `created_by` BIGINT UNSIGNED NULL AFTER `is_active`,
MODIFY COLUMN `created_at` TIMESTAMP(0) NULL AFTER `created_by`,
MODIFY COLUMN `updated_at` TIMESTAMP(0) NULL AFTER `created_at`;
ALTER TABLE `radio_giveaways`
ADD COLUMN IF NOT EXISTS `title` VARCHAR(255) NOT NULL AFTER `id`,
ADD COLUMN IF NOT EXISTS `description` TEXT NULL AFTER `title`,
ADD COLUMN IF NOT EXISTS `prize` VARCHAR(255) NULL AFTER `description`,
ADD COLUMN IF NOT EXISTS `prize_amount` INT NOT NULL DEFAULT 0 AFTER `prize`,
ADD COLUMN IF NOT EXISTS `prize_currency` VARCHAR(50) NULL AFTER `prize_amount`,
ADD COLUMN IF NOT EXISTS `start_date` TIMESTAMP(0) NULL AFTER `prize_currency`,
ADD COLUMN IF NOT EXISTS `end_date` TIMESTAMP(0) NULL AFTER `start_date`,
ADD COLUMN IF NOT EXISTS `winner_id` BIGINT UNSIGNED NULL AFTER `end_date`,
ADD COLUMN IF NOT EXISTS `is_active` BOOLEAN NOT NULL DEFAULT true AFTER `winner_id`,
ADD COLUMN IF NOT EXISTS `created_by` BIGINT UNSIGNED NULL AFTER `is_active`,
MODIFY COLUMN `created_at` TIMESTAMP(0) NULL AFTER `created_by`,
MODIFY COLUMN `updated_at` TIMESTAMP(0) NULL AFTER `created_at`;
@@ -0,0 +1,114 @@
-- CMS-owned tables required by the restored administration modules.
-- Idempotent and deliberately free of foreign keys so it can be applied to
-- existing emulator databases whose users table may use different engines.
CREATE TABLE IF NOT EXISTS website_tickets (
id INT NOT NULL AUTO_INCREMENT,
subject VARCHAR(255) NOT NULL,
category VARCHAR(50) NOT NULL DEFAULT 'general',
priority VARCHAR(20) NOT NULL DEFAULT 'normal',
status VARCHAR(20) NOT NULL DEFAULT 'open',
creator_id INT NOT NULL,
assignee_id INT NULL,
closed_at DATETIME NULL,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (id),
KEY website_tickets_creator_idx (creator_id),
KEY website_tickets_assignee_idx (assignee_id),
KEY website_tickets_status_idx (status)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS website_ticket_messages (
id INT NOT NULL AUTO_INCREMENT,
ticket_id INT NOT NULL,
user_id INT NOT NULL,
message TEXT NOT NULL,
is_staff INT NOT NULL DEFAULT 0,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id),
KEY website_ticket_messages_ticket_idx (ticket_id),
KEY website_ticket_messages_user_idx (user_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS website_ticket_templates (
id INT NOT NULL AUTO_INCREMENT,
title VARCHAR(255) NOT NULL,
content TEXT NOT NULL,
category VARCHAR(50) NOT NULL DEFAULT 'general',
sort_order INT NOT NULL DEFAULT 0,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS user_watch (
id INT NOT NULL AUTO_INCREMENT,
staff_id INT NOT NULL,
target_user_id INT NOT NULL,
reason VARCHAR(255) NOT NULL DEFAULT '',
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id),
UNIQUE KEY user_watch_unique (staff_id, target_user_id),
KEY user_watch_target_idx (target_user_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS admin_audit_log (
id INT NOT NULL AUTO_INCREMENT,
user_id INT NOT NULL,
action VARCHAR(191) NOT NULL DEFAULT '',
target VARCHAR(191) NOT NULL DEFAULT '',
target_id INT NULL,
details TEXT NULL,
`before` TEXT NULL,
`after` TEXT NULL,
diff TEXT NULL,
ip_address VARCHAR(45) NULL,
created_at VARCHAR(64) NOT NULL DEFAULT '',
updated_at VARCHAR(64) NULL,
PRIMARY KEY (id),
KEY admin_audit_log_user_idx (user_id),
KEY admin_audit_log_target_idx (target, target_id),
KEY admin_audit_log_action_idx (action)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS acl_roles (
id INT NOT NULL AUTO_INCREMENT,
slug VARCHAR(191) NOT NULL,
title VARCHAR(255) NOT NULL DEFAULT '',
description TEXT NULL,
created_at DATETIME NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (id),
UNIQUE KEY acl_roles_slug_unique (slug)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS acl_permissions (
id INT NOT NULL AUTO_INCREMENT,
slug VARCHAR(191) NOT NULL,
title VARCHAR(255) NOT NULL DEFAULT '',
created_at DATETIME NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (id),
UNIQUE KEY acl_permissions_slug_unique (slug)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS acl_model_roles (
id INT NOT NULL AUTO_INCREMENT,
model_type VARCHAR(191) NOT NULL DEFAULT 'User',
model_id INT NOT NULL,
role_id INT NOT NULL,
PRIMARY KEY (id),
KEY acl_model_roles_role_idx (role_id),
UNIQUE KEY acl_model_roles_unique (model_type, model_id, role_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS acl_model_permissions (
id INT NOT NULL AUTO_INCREMENT,
model_type VARCHAR(191) NOT NULL DEFAULT 'Role',
model_id INT NOT NULL,
permission_id INT NOT NULL,
PRIMARY KEY (id),
KEY acl_model_permissions_model_idx (model_id),
KEY acl_model_permissions_permission_idx (permission_id),
UNIQUE KEY acl_model_permissions_unique (model_type, model_id, permission_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
@@ -0,0 +1,37 @@
-- Normalize ACL discriminator casing and ensure every emulator rank has a CMS role.
UPDATE `acl_model_permissions` SET `model_type` = 'Role' WHERE LOWER(`model_type`) = 'role';
UPDATE `acl_model_roles` SET `model_type` = 'User' WHERE LOWER(`model_type`) = 'user';
INSERT INTO `acl_roles` (`slug`, `title`, `description`)
SELECT CONCAT('rank_', pr.id), COALESCE(NULLIF(pr.rank_name, ''), CONCAT('Rank ', pr.id)), 'CMS role synchronized from permission_ranks'
FROM `permission_ranks` pr
WHERE NOT EXISTS (SELECT 1 FROM `acl_roles` ar WHERE ar.slug = CONCAT('rank_', pr.id));
-- Preserve the former moderator fallback as explicit ACL data.
INSERT INTO `acl_model_permissions` (`model_type`, `model_id`, `permission_id`)
SELECT 'Role', ar.id, ap.id
FROM `permission_ranks` pr
JOIN `acl_roles` ar ON ar.slug = CONCAT('rank_', pr.id)
JOIN `acl_permissions` ap ON (
pr.id >= 3 AND (
ap.slug = 'mod.dashboard' OR ap.slug = 'mod.actions' OR
(ap.slug LIKE 'mod.%' AND ap.slug LIKE '%.view')
)
)
WHERE NOT EXISTS (
SELECT 1 FROM `acl_model_permissions` amp
WHERE amp.model_type = 'Role' AND amp.model_id = ar.id AND amp.permission_id = ap.id
);
-- Preserve the former administrator read-only fallback as explicit ACL data.
INSERT INTO `acl_model_permissions` (`model_type`, `model_id`, `permission_id`)
SELECT 'Role', ar.id, ap.id
FROM `permission_ranks` pr
JOIN `acl_roles` ar ON ar.slug = CONCAT('rank_', pr.id)
JOIN `acl_permissions` ap ON (
pr.id >= 6 AND (ap.slug = 'admin.dashboard' OR (ap.slug LIKE 'admin.%' AND ap.slug LIKE '%.view'))
)
WHERE NOT EXISTS (
SELECT 1 FROM `acl_model_permissions` amp
WHERE amp.model_type = 'Role' AND amp.model_id = ar.id AND amp.permission_id = ap.id
);
@@ -0,0 +1,104 @@
-- Seed every permission used by the CMS. Migration 0010 created the ACL
-- tables but did not populate acl_permissions, so migration 0011 could not
-- assign any dashboard permission to rank roles.
INSERT INTO `acl_permissions` (`slug`, `title`) VALUES
('admin.dashboard', 'Admin dashboard'),
('admin.users.view', 'View users'),
('admin.users.edit', 'Edit users'),
('admin.users.ban', 'Ban users'),
('admin.users.reset_password', 'Reset user passwords'),
('admin.room.view', 'View rooms'),
('admin.room.edit', 'Edit rooms'),
('admin.room.delete', 'Delete rooms'),
('admin.bans.view', 'View bans'),
('admin.news.view', 'View news'),
('admin.news.edit', 'Edit news'),
('admin.logs.view', 'View logs'),
('admin.settings.view', 'View settings'),
('admin.settings.edit', 'Edit settings'),
('admin.permissions.manage', 'Manage permissions'),
('admin.shop.view', 'View shop'),
('admin.shop.edit', 'Edit shop'),
('admin.wordfilter.view', 'View word filter'),
('admin.wordfilter.edit', 'Edit word filter'),
('admin.catalog.view', 'View catalog'),
('admin.catalog.edit', 'Edit catalog'),
('admin.rcon.execute', 'Execute RCON commands'),
('admin.export', 'Export data'),
('admin.radio.view', 'View radio admin'),
('admin.radio.edit', 'Edit radio admin'),
('admin.prefixes.view', 'View prefixes'),
('admin.prefixes.edit', 'Edit prefixes'),
('admin.tickets.view', 'View tickets'),
('admin.tickets.edit', 'Edit tickets'),
('admin.moderation.view', 'View moderation'),
('admin.moderation.edit', 'Edit moderation'),
('admin.events.view', 'View events'),
('admin.events.edit', 'Edit events'),
('admin.analytics.view', 'View analytics'),
('admin.analytics.export', 'Export analytics'),
('admin.devops.view', 'View DevOps'),
('admin.devops.edit', 'Edit DevOps'),
('admin.polls.view', 'View polls'),
('admin.polls.edit', 'Edit polls'),
('admin.notifications.view', 'View notifications'),
('admin.notifications.edit', 'Edit notifications'),
('admin.pages.view', 'View CMS pages'),
('admin.pages.edit', 'Edit CMS pages'),
('admin.banners.view', 'View banners'),
('admin.banners.edit', 'Edit banners'),
('admin.assets.import', 'Import assets'),
('mod.dashboard', 'Moderator dashboard'),
('mod.cfh.view', 'View calls for help'),
('mod.cfh.edit', 'Manage calls for help'),
('mod.actions', 'Execute moderator actions'),
('mod.team.view', 'View moderator team'),
('mod.tickets.view', 'View moderator tickets'),
('mod.tickets.edit', 'Edit moderator tickets'),
('mod.users.view', 'View users in moderation'),
('mod.bans.view', 'View bans in moderation')
ON DUPLICATE KEY UPDATE `title` = VALUES(`title`);
-- Keep rank roles synchronized for databases where 0011 already ran.
INSERT INTO `acl_roles` (`slug`, `title`)
SELECT CONCAT('rank_', pr.id), COALESCE(NULLIF(pr.rank_name, ''), CONCAT('Rank ', pr.id))
FROM `permission_ranks` pr
WHERE NOT EXISTS (
SELECT 1 FROM `acl_roles` ar WHERE ar.slug = CONCAT('rank_', pr.id)
);
-- Preserve moderator access as explicit ACL assignments.
INSERT INTO `acl_model_permissions` (`model_type`, `model_id`, `permission_id`)
SELECT 'Role', ar.id, ap.id
FROM `permission_ranks` pr
JOIN `acl_roles` ar ON ar.slug = CONCAT('rank_', pr.id)
JOIN `acl_permissions` ap ON (
pr.id >= 3 AND (
ap.slug = 'mod.dashboard' OR ap.slug = 'mod.actions' OR
(ap.slug LIKE 'mod.%' AND ap.slug LIKE '%.view')
)
)
WHERE NOT EXISTS (
SELECT 1 FROM `acl_model_permissions` amp
WHERE amp.model_type = 'Role'
AND amp.model_id = ar.id
AND amp.permission_id = ap.id
);
-- Preserve administrator read access as explicit ACL assignments.
INSERT INTO `acl_model_permissions` (`model_type`, `model_id`, `permission_id`)
SELECT 'Role', ar.id, ap.id
FROM `permission_ranks` pr
JOIN `acl_roles` ar ON ar.slug = CONCAT('rank_', pr.id)
JOIN `acl_permissions` ap ON (
pr.id >= 6 AND (
ap.slug = 'admin.dashboard' OR
(ap.slug LIKE 'admin.%' AND ap.slug LIKE '%.view')
)
)
WHERE NOT EXISTS (
SELECT 1 FROM `acl_model_permissions` amp
WHERE amp.model_type = 'Role'
AND amp.model_id = ar.id
AND amp.permission_id = ap.id
);
@@ -0,0 +1,136 @@
CREATE TABLE IF NOT EXISTS `website_event_types` (
`id` INTEGER NOT NULL AUTO_INCREMENT,
`name` VARCHAR(100) NOT NULL,
`slug` VARCHAR(100) NOT NULL,
`description` VARCHAR(500) NOT NULL DEFAULT '',
`color` VARCHAR(20) NOT NULL DEFAULT '#3b82f6',
`icon` VARCHAR(50) NOT NULL DEFAULT 'calendar',
`is_active` INTEGER NOT NULL DEFAULT 1,
`min_rank` INTEGER NOT NULL DEFAULT 0,
UNIQUE INDEX `website_event_types_slug_key`(`slug`),
PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS `website_events` (
`id` INTEGER NOT NULL AUTO_INCREMENT,
`title` VARCHAR(255) NOT NULL,
`description` TEXT NOT NULL,
`type_id` INTEGER NOT NULL,
`host_user_id` INTEGER NOT NULL,
`room_id` INTEGER NULL,
`starts_at` DATETIME(3) NOT NULL,
`ends_at` DATETIME(3) NULL,
`max_players` INTEGER NULL,
`is_recurring` INTEGER NOT NULL DEFAULT 0,
`recurrence_rule` VARCHAR(255) NULL,
`status` VARCHAR(20) NOT NULL DEFAULT 'draft',
`image` VARCHAR(500) NULL,
`created_at` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
`updated_at` DATETIME(3) NOT NULL,
INDEX `website_events_type_id_idx`(`type_id`),
INDEX `website_events_host_user_id_idx`(`host_user_id`),
INDEX `website_events_starts_at_idx`(`starts_at`),
INDEX `website_events_status_idx`(`status`),
PRIMARY KEY (`id`),
CONSTRAINT `website_events_type_id_fkey` FOREIGN KEY (`type_id`) REFERENCES `website_event_types`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS `website_event_registrations` (
`id` INTEGER NOT NULL AUTO_INCREMENT,
`event_id` INTEGER NOT NULL,
`user_id` INTEGER NOT NULL,
`created_at` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
INDEX `website_event_registrations_user_id_idx`(`user_id`),
UNIQUE INDEX `website_event_registrations_event_id_user_id_key`(`event_id`, `user_id`),
PRIMARY KEY (`id`),
CONSTRAINT `website_event_registrations_event_id_fkey` FOREIGN KEY (`event_id`) REFERENCES `website_events`(`id`) ON DELETE CASCADE ON UPDATE CASCADE
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS `website_event_prizes` (
`id` INTEGER NOT NULL AUTO_INCREMENT,
`event_id` INTEGER NOT NULL,
`position` INTEGER NOT NULL DEFAULT 1,
`prize_type` VARCHAR(20) NOT NULL DEFAULT 'badge',
`badge_code` VARCHAR(50) NULL,
`credits` INTEGER NOT NULL DEFAULT 0,
`pixels` INTEGER NOT NULL DEFAULT 0,
`points` INTEGER NOT NULL DEFAULT 0,
`item_id` INTEGER NULL,
`description` VARCHAR(255) NOT NULL DEFAULT '',
INDEX `website_event_prizes_event_id_idx`(`event_id`),
PRIMARY KEY (`id`),
CONSTRAINT `website_event_prizes_event_id_fkey` FOREIGN KEY (`event_id`) REFERENCES `website_events`(`id`) ON DELETE CASCADE ON UPDATE CASCADE
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS `website_event_winners` (
`id` INTEGER NOT NULL AUTO_INCREMENT,
`event_id` INTEGER NOT NULL,
`user_id` INTEGER NOT NULL,
`position` INTEGER NOT NULL DEFAULT 1,
`created_at` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
INDEX `website_event_winners_user_id_idx`(`user_id`),
UNIQUE INDEX `website_event_winners_event_id_user_id_key`(`event_id`, `user_id`),
PRIMARY KEY (`id`),
CONSTRAINT `website_event_winners_event_id_fkey` FOREIGN KEY (`event_id`) REFERENCES `website_events`(`id`) ON DELETE CASCADE ON UPDATE CASCADE
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS `website_polls` (
`id` INTEGER NOT NULL AUTO_INCREMENT,
`title` VARCHAR(255) NOT NULL,
`description` TEXT NULL,
`status` VARCHAR(20) NOT NULL DEFAULT 'draft',
`show_results` INTEGER NOT NULL DEFAULT 1,
`multiple_choice` INTEGER NOT NULL DEFAULT 0,
`starts_at` DATETIME(3) NULL,
`ends_at` DATETIME(3) NULL,
`created_at` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
`updated_at` DATETIME(3) NOT NULL,
INDEX `website_polls_status_idx`(`status`),
PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS `website_poll_questions` (
`id` INTEGER NOT NULL AUTO_INCREMENT,
`poll_id` INTEGER NOT NULL,
`question` VARCHAR(500) NOT NULL,
`type` VARCHAR(20) NOT NULL DEFAULT 'single',
`sort_order` INTEGER NOT NULL DEFAULT 0,
`options` TEXT NOT NULL,
INDEX `website_poll_questions_poll_id_idx`(`poll_id`),
PRIMARY KEY (`id`),
CONSTRAINT `website_poll_questions_poll_id_fkey` FOREIGN KEY (`poll_id`) REFERENCES `website_polls`(`id`) ON DELETE CASCADE ON UPDATE CASCADE
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS `website_poll_votes` (
`id` INTEGER NOT NULL AUTO_INCREMENT,
`question_id` INTEGER NOT NULL,
`user_id` INTEGER NOT NULL,
`answer` VARCHAR(500) NOT NULL,
`created_at` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
INDEX `website_poll_votes_user_id_idx`(`user_id`),
UNIQUE INDEX `website_poll_votes_question_id_user_id_key`(`question_id`, `user_id`),
PRIMARY KEY (`id`),
CONSTRAINT `website_poll_votes_question_id_fkey` FOREIGN KEY (`question_id`) REFERENCES `website_poll_questions`(`id`) ON DELETE CASCADE ON UPDATE CASCADE
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS `website_banners` (
`id` INT NOT NULL AUTO_INCREMENT,
`title` VARCHAR(255) NOT NULL DEFAULT '', `subtitle` VARCHAR(500) NOT NULL DEFAULT '',
`image` VARCHAR(500) NOT NULL DEFAULT '', `link` VARCHAR(500) NOT NULL DEFAULT '',
`color` VARCHAR(20) NOT NULL DEFAULT '', `is_active` INT NOT NULL DEFAULT 1,
`sort_order` INT NOT NULL DEFAULT 0, `start_date` VARCHAR(50) NULL, `end_date` VARCHAR(50) NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS `custom_prefixes` (
`id` INT NOT NULL AUTO_INCREMENT, `user_id` INT NOT NULL, `text` VARCHAR(50) NOT NULL,
`color` VARCHAR(255) NOT NULL DEFAULT '#FFFFFF', `icon` VARCHAR(50) NOT NULL DEFAULT '',
`effect` VARCHAR(50) NOT NULL DEFAULT '', `active` TINYINT(1) NOT NULL DEFAULT 0,
PRIMARY KEY (`id`), INDEX `idx_custom_prefix_user` (`user_id`, `active`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS `custom_prefix_blacklist` (
`id` INT NOT NULL AUTO_INCREMENT, `word` VARCHAR(100) NOT NULL, PRIMARY KEY (`id`), UNIQUE KEY `uq_prefix_word` (`word`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS `custom_prefix_settings` (
`key` VARCHAR(100) NOT NULL, `value` TEXT NOT NULL, PRIMARY KEY (`key`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
@@ -0,0 +1,35 @@
UPDATE `acl_model_permissions` SET `model_type` = 'Role' WHERE LOWER(`model_type`) = 'role';
UPDATE `acl_model_roles` SET `model_type` = 'User' WHERE LOWER(`model_type`) = 'user';
INSERT INTO `acl_permissions` (`slug`, `title`) VALUES
('admin.assets.import', 'Import assets'),
('admin.permissions.manage', 'Manage permissions')
ON DUPLICATE KEY UPDATE `title` = VALUES(`title`);
INSERT INTO `acl_roles` (`slug`, `title`, `description`)
SELECT CONCAT('rank_', pr.id), COALESCE(NULLIF(pr.rank_name, ''), CONCAT('Rank ', pr.id)), 'CMS role synchronized from permission_ranks'
FROM `permission_ranks` pr
WHERE NOT EXISTS (SELECT 1 FROM `acl_roles` ar WHERE ar.slug = CONCAT('rank_', pr.id));
INSERT INTO `acl_model_permissions` (`model_type`, `model_id`, `permission_id`)
SELECT 'Role', ar.id, ap.id
FROM `permission_ranks` pr
JOIN `acl_roles` ar ON ar.slug = CONCAT('rank_', pr.id)
CROSS JOIN `acl_permissions` ap
WHERE pr.id = (SELECT MAX(id) FROM `permission_ranks`)
AND ap.slug IN ('admin.permissions.manage', 'admin.assets.import')
AND NOT EXISTS (
SELECT 1 FROM `acl_model_permissions` amp
WHERE amp.model_type = 'Role' AND amp.model_id = ar.id AND amp.permission_id = ap.id
);
INSERT INTO `acl_model_permissions` (`model_type`, `model_id`, `permission_id`)
SELECT 'Role', ar.id, ap.id
FROM `website_housekeeping_permissions` whp
JOIN `acl_permissions` ap ON ap.slug = whp.permission
JOIN `permission_ranks` pr ON pr.id >= whp.min_rank
JOIN `acl_roles` ar ON ar.slug = CONCAT('rank_', pr.id)
WHERE NOT EXISTS (
SELECT 1 FROM `acl_model_permissions` amp
WHERE amp.model_type = 'Role' AND amp.model_id = ar.id AND amp.permission_id = ap.id
);
@@ -0,0 +1,33 @@
INSERT INTO `acl_permissions` (`slug`, `title`) VALUES
('admin.radio.view', 'View radio admin'),
('admin.radio.edit', 'Edit radio admin')
ON DUPLICATE KEY UPDATE `title` = VALUES(`title`);
-- Rank >= 6 gets radio view (matches other admin.*.view grants from 0012).
INSERT INTO `acl_model_permissions` (`model_type`, `model_id`, `permission_id`)
SELECT 'Role', ar.id, ap.id
FROM `permission_ranks` pr
JOIN `acl_roles` ar ON ar.slug = CONCAT('rank_', pr.id)
JOIN `acl_permissions` ap ON ap.slug = 'admin.radio.view'
WHERE pr.id >= 6
AND NOT EXISTS (
SELECT 1 FROM `acl_model_permissions` amp
WHERE amp.model_type = 'Role'
AND amp.model_id = ar.id
AND amp.permission_id = ap.id
);
-- Highest rank gets radio edit (same pattern as assets.import / permissions.manage).
INSERT INTO `acl_model_permissions` (`model_type`, `model_id`, `permission_id`)
SELECT 'Role', ar.id, ap.id
FROM `permission_ranks` pr
JOIN `acl_roles` ar ON ar.slug = CONCAT('rank_', pr.id)
CROSS JOIN `acl_permissions` ap
WHERE pr.id = (SELECT MAX(id) FROM `permission_ranks`)
AND ap.slug = 'admin.radio.edit'
AND NOT EXISTS (
SELECT 1 FROM `acl_model_permissions` amp
WHERE amp.model_type = 'Role'
AND amp.model_id = ar.id
AND amp.permission_id = ap.id
);
@@ -0,0 +1,6 @@
ALTER TABLE `website_housekeeping_permissions`
ADD COLUMN `group_name` VARCHAR(100) DEFAULT NULL AFTER `description`,
ADD COLUMN `depends_on` VARCHAR(255) DEFAULT NULL AFTER `group_name`,
ADD COLUMN `updated_by` INT DEFAULT NULL AFTER `updated_at`;
-- Migration tracking (no model change needed for audit log usage)
@@ -0,0 +1,4 @@
-- CMS JWT invalidation counter on users (stateless NextAuth sessions).
-- Idempotent (MariaDB).
ALTER TABLE users
ADD COLUMN IF NOT EXISTS website_jwt_version INT NOT NULL DEFAULT 0;
@@ -0,0 +1,69 @@
-- Repair admin ACL so ranks that can open the panel also see/use admin sections.
-- Root cause: sidebar filters by ACL; seed only granted some admin.*.view rows,
-- and later sync from housekeeping could leave mid/high ranks incomplete.
-- 1) Every rank that already has admin.dashboard gets ALL admin.* permissions.
INSERT INTO `acl_model_permissions` (`model_type`, `model_id`, `permission_id`)
SELECT 'Role', ar.id, ap.id
FROM `acl_roles` ar
JOIN `acl_permissions` ap ON ap.slug LIKE 'admin.%'
WHERE EXISTS (
SELECT 1
FROM `acl_model_permissions` amp
JOIN `acl_permissions` apdash ON apdash.id = amp.permission_id
WHERE amp.model_type = 'Role'
AND amp.model_id = ar.id
AND apdash.slug = 'admin.dashboard'
)
AND NOT EXISTS (
SELECT 1
FROM `acl_model_permissions` amp2
WHERE amp2.model_type = 'Role'
AND amp2.model_id = ar.id
AND amp2.permission_id = ap.id
);
-- 2) Safety net matching original seed: ranks >= 6 get all admin.*.view + dashboard.
INSERT INTO `acl_model_permissions` (`model_type`, `model_id`, `permission_id`)
SELECT 'Role', ar.id, ap.id
FROM `permission_ranks` pr
JOIN `acl_roles` ar ON ar.slug = CONCAT('rank_', pr.id)
JOIN `acl_permissions` ap ON (
ap.slug = 'admin.dashboard'
OR (ap.slug LIKE 'admin.%' AND ap.slug LIKE '%.view')
)
WHERE pr.id >= 6
AND NOT EXISTS (
SELECT 1
FROM `acl_model_permissions` amp
WHERE amp.model_type = 'Role'
AND amp.model_id = ar.id
AND amp.permission_id = ap.id
);
-- 3) Ranks >= 7 also get edit/manage/execute tools used by the sidebar.
INSERT INTO `acl_model_permissions` (`model_type`, `model_id`, `permission_id`)
SELECT 'Role', ar.id, ap.id
FROM `permission_ranks` pr
JOIN `acl_roles` ar ON ar.slug = CONCAT('rank_', pr.id)
JOIN `acl_permissions` ap ON (
(ap.slug LIKE 'admin.%' AND ap.slug LIKE '%.edit')
OR ap.slug IN (
'admin.permissions.manage',
'admin.rcon.execute',
'admin.assets.import',
'admin.export',
'admin.analytics.export',
'admin.users.ban',
'admin.users.reset_password',
'admin.room.delete'
)
)
WHERE pr.id >= 7
AND NOT EXISTS (
SELECT 1
FROM `acl_model_permissions` amp
WHERE amp.model_type = 'Role'
AND amp.model_id = ar.id
AND amp.permission_id = ap.id
);
@@ -0,0 +1,9 @@
CREATE TABLE IF NOT EXISTS `website_login_logs` (
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
`user_id` INT NOT NULL,
`ip` VARCHAR(45) NOT NULL,
`user_agent` VARCHAR(512) DEFAULT NULL,
`created_at` TIMESTAMP NULL DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `website_login_logs_user_id_index` (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;