Преглед на файлове

chore: add deployment recovery scripts

chenyu преди 2 месеца
родител
ревизия
25dabba5a5
променени са 3 файла, в които са добавени 125 реда и са изтрити 0 реда
  1. 47 0
      backend/prisma/deploy/20260808_reset_failed_invite_migration.sql
  2. 72 0
      backend/prisma/deploy/20260808_stylist_release.sql
  3. 6 0
      frontend/src/index.css

+ 47 - 0
backend/prisma/deploy/20260808_reset_failed_invite_migration.sql

@@ -0,0 +1,47 @@
+-- Reset only the failed Prisma migration: 20260806_add_simulation_invite_code
+-- Target: MySQL 5.7+ / MySQL 8+
+--
+-- This removes the invitation-code column and its unique index, then deletes
+-- Prisma's failed migration record. It does NOT touch customers, templates,
+-- simulation tasks, beta applications, or other business data.
+--
+-- Important: any invitation codes issued after this migration will be removed.
+-- On the next backend startup, Prisma will apply the migration again and
+-- regenerate/backfill the required codes.
+
+DELIMITER $$
+
+CREATE PROCEDURE `aitori_reset_failed_invite_migration_20260808`()
+BEGIN
+  IF EXISTS (
+    SELECT 1
+    FROM information_schema.STATISTICS
+    WHERE TABLE_SCHEMA = DATABASE()
+      AND TABLE_NAME = 'hairstylist_user'
+      AND INDEX_NAME = 'hairstylist_user_simulation_invite_code_key'
+  ) THEN
+    ALTER TABLE `hairstylist_user`
+      DROP INDEX `hairstylist_user_simulation_invite_code_key`;
+  END IF;
+
+  IF EXISTS (
+    SELECT 1
+    FROM information_schema.COLUMNS
+    WHERE TABLE_SCHEMA = DATABASE()
+      AND TABLE_NAME = 'hairstylist_user'
+      AND COLUMN_NAME = 'simulation_invite_code'
+  ) THEN
+    ALTER TABLE `hairstylist_user`
+      DROP COLUMN `simulation_invite_code`;
+  END IF;
+END$$
+
+DELIMITER ;
+
+CALL `aitori_reset_failed_invite_migration_20260808`();
+DROP PROCEDURE `aitori_reset_failed_invite_migration_20260808`;
+
+-- Clear Prisma's failed migration state so `prisma migrate deploy` can run
+-- the migration again from its first statement.
+DELETE FROM `_prisma_migrations`
+WHERE `migration_name` = '20260806_add_simulation_invite_code';

+ 72 - 0
backend/prisma/deploy/20260808_stylist_release.sql

@@ -0,0 +1,72 @@
+-- Aitori Stylist release deployment script
+-- Target: MySQL 5.7+ / MySQL 8+
+-- This script is safe to run once on the production database. It is also
+-- idempotent for the schema changes, so a rerun will not add the column or
+-- index a second time.
+
+DELIMITER $$
+
+CREATE PROCEDURE `aitori_apply_stylist_release_20260808`()
+BEGIN
+  IF NOT EXISTS (
+    SELECT 1
+    FROM information_schema.COLUMNS
+    WHERE TABLE_SCHEMA = DATABASE()
+      AND TABLE_NAME = 'hairstylist_user'
+      AND COLUMN_NAME = 'simulation_invite_code'
+  ) THEN
+    ALTER TABLE `hairstylist_user`
+      ADD COLUMN `simulation_invite_code` VARCHAR(8) NULL AFTER `password`;
+  END IF;
+
+  IF NOT EXISTS (
+    SELECT 1
+    FROM information_schema.STATISTICS
+    WHERE TABLE_SCHEMA = DATABASE()
+      AND TABLE_NAME = 'hairstylist_user'
+      AND INDEX_NAME = 'hairstylist_user_simulation_invite_code_key'
+  ) THEN
+    CREATE UNIQUE INDEX `hairstylist_user_simulation_invite_code_key`
+      ON `hairstylist_user` (`simulation_invite_code`);
+  END IF;
+END$$
+
+DELIMITER ;
+
+CALL `aitori_apply_stylist_release_20260808`();
+DROP PROCEDURE `aitori_apply_stylist_release_20260808`;
+
+-- Give the standard local/demo stylist the documented code without replacing
+-- a code that has already been issued to a production stylist.
+UPDATE `hairstylist_user`
+SET `simulation_invite_code` = 'K8HUY7SS'
+WHERE `email` = 'stylist@aitori.jp'
+  AND `is_deleted` = 0
+  AND (`simulation_invite_code` IS NULL OR `simulation_invite_code` = '');
+
+-- Backfill stable 8-character invitation codes for every existing active
+-- stylist that does not have one yet. "S" + a zero-padded base-36 id keeps
+-- the values in the allowed uppercase alphanumeric format and avoids random
+-- collisions for normal account id ranges.
+UPDATE `hairstylist_user`
+SET `simulation_invite_code` = CONCAT(
+  'S',
+  LPAD(UPPER(CONV(`id`, 10, 36)), 7, '0')
+)
+WHERE `is_deleted` = 0
+  AND (`simulation_invite_code` IS NULL OR `simulation_invite_code` = '');
+
+-- The template package is a ZIP file used by the AI service, not a browser
+-- image. Set matching system template cover URLs to their display previews.
+-- template_url remains unchanged and continues to be sent to the AI service.
+UPDATE `hair_template`
+SET `cover_url` = CASE `template_name`
+  WHEN '居家造型' THEN '/images/mvp-simulation-result-10/home-styling.jpg'
+  WHEN '居家造型30天后造型' THEN '/images/mvp-simulation-result-10/after-30-days.jpg'
+  WHEN '居家造型60天后造型' THEN '/images/mvp-simulation-result-10/after-60-days.jpg'
+  ELSE `cover_url`
+END
+WHERE `is_system_template` = 'YES'
+  AND `is_deleted` = 0
+  AND `template_name` IN ('居家造型', '居家造型30天后造型', '居家造型60天后造型')
+  AND (`cover_url` = '' OR `cover_url` REGEXP '\\.zip(\\?.*)?$');

+ 6 - 0
frontend/src/index.css

@@ -750,6 +750,12 @@ footer.footer-light {
 
 .beta-apply-navbar .container-fluid {
   min-height: 86px;
+  display: flex;
+  align-items: center;
+}
+
+.beta-apply-navbar .col-auto.d-flex {
+  align-self: center;
   align-items: center;
 }