Compare commits

...

4 commits

Author SHA1 Message Date
f311c92dbb docs: add deployment checklist for database consolidation
IMPORTANT: This migration is BLOCKED until Impress 2020 is retired.

Created comprehensive deployment guide documenting:
- Why this migration is blocked (Impress 2020 uses openneo_id directly)
- Two paths forward: retire Impress 2020 (recommended) or coordinated update
- Complete step-by-step deployment checklist for when ready
- Rollback procedures
- Risk assessment and mitigations
- Success criteria and timeline estimates

This ensures we don't accidentally deploy this change before addressing
the Impress 2020 dependency.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-02 07:07:57 +00:00
9ba94f9f4b chore: document legacy openneo_id migrations and update references
This commit completes the database consolidation cleanup by documenting
the historical migrations and updating all references to reflect the
single-database architecture.

Changes:
- db/openneo_id_migrate/README.md: Created comprehensive documentation
  explaining the history of the separate database and why these migrations
  are preserved but no longer runnable
- db/openneo_id_schema.rb: Deleted (no longer needed)
- README.md: Updated to reflect single-database architecture
  - Removed mentions of "two databases"
  - Updated "OpenNeo ID Database" section to "Authentication Architecture"
  - Added reference to historical context in db/openneo_id_migrate/README.md
- deploy/setup.yml: Removed openneo_id database creation and privileges
  for future deployments
- db/migrate/20240401124200_increase_username_length.rb: Updated comment
  to note this was historically paired with an openneo_id migration

The codebase now fully reflects the consolidated single-database architecture.
The legacy migration files in db/openneo_id_migrate/ are preserved for
historical reference only.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-02 07:02:43 +00:00
2c21269a16 feat: migrate AuthUser to main database
This commit completes the migration to consolidate the openneo_id database
into the main openneo_impress database.

Changes:
- AuthUser: Changed from AuthRecord to ApplicationRecord
- AuthUser: Removed explicit table_name (Rails infers 'auth_users')
- AuthUser: Removed all temporary write lock code
- AuthUser: Added TODO comment about future table merge opportunity
- User: Added TODO comment about simplifying remote_id relationship
- AuthRecord: Deleted (no longer needed)
- ApplicationController: Removed temporary rescue_from handler
- database.yml: Removed openneo_id database configuration entirely
- database.yml: Simplified from multi-database (primary:) to single-database structure

The application now runs as a single-database Rails app. The auth_users table
lives in the main openneo_impress database alongside the users table, with
the remote_id relationship preserved.

Next steps for production:
1. Deploy Phase 1 (write lock)
2. Run the CopyAuthUsersTableToMainDatabase migration
3. Deploy this commit (Phase 2)
4. Verify everything works
5. Drop the openneo_id database

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-02 06:55:11 +00:00
604a8667cf feat: add write lock for AuthUser migration preparation
This commit implements a temporary write lock on the AuthUser model to
prepare for consolidating the openneo_id database into the main database.

Changes:
- AuthUser: Added before_save/before_destroy callbacks that raise
  TemporarilyReadOnly exception to prevent any writes
- AuthUser: Override update_tracked_fields! to silently skip Devise login
  tracking (prevents crashes during login while maintaining read access)
- ApplicationController: Added rescue_from handler for TemporarilyReadOnly
  that redirects to root with a friendly maintenance message
- Migration: Created CopyAuthUsersTableToMainDatabase to copy
  openneo_id.users → openneo_impress.auth_users (preserves all IDs)

This allows us to run the data copy migration in production while:
- Keeping existing users able to log in and use the site
- Blocking new registrations, settings changes, and NeoPass connections
- Losing some login tracking data (acceptable tradeoff)

Next step: Deploy this, then run the migration in production while the
table is stable.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-02 06:50:55 +00:00
12 changed files with 440 additions and 110 deletions

View file

@ -11,7 +11,7 @@ DTI is a Rails application with a React-based outfit editor, backed by MySQL dat
### Core Components
- **Rails backend** (Ruby 3.4, Rails 8.0): Serves web pages, API endpoints, and manages data
- **MySQL databases**: Primary database (`openneo_impress`) + legacy auth database (`openneo_id`)
- **MySQL database**: Single database (`openneo_impress`) containing all application and authentication data
- **React outfit editor**: Embedded in `app/javascript/wardrobe-2020/`, provides the main customization UI
- **Modeling system**: Crowdsources pet/item appearance data by fetching from Neopets APIs when users load their pets
@ -98,7 +98,7 @@ app/
```
config/
├── routes.rb # All Rails routes
├── database.yml # Multi-database setup (main + openneo_id)
├── database.yml # Database configuration
└── environments/
└── *.rb # Env-specific config (incl. impress_2020_origin)
```
@ -117,7 +117,7 @@ config/
- **Backend**: Ruby on Rails (Ruby 3.4, Rails 8.0)
- **Frontend**: Mix of Rails views (Turbo/HAML) and React (for outfit editor)
- **Database**: MySQL (two databases: `openneo_impress`, `openneo_id`)
- **Database**: MySQL (`openneo_impress`)
- **Styling**: CSS, Sass (moving toward modern Rails conventions)
- **External Integrations**:
- **Neopets.com**: Legacy Flash/AMF protocol for pet appearance data (modeling)
@ -129,11 +129,15 @@ config/
## Development Notes
### OpenNeo ID Database
### Authentication Architecture
The `openneo_id` database is a legacy from when authentication was a separate service ("OpenNeo ID") meant to unify auth across multiple OpenNeo projects. DTI was the only project that succeeded, so the apps were merged—but the database split remains for now.
Authentication data lives in the `auth_users` table (managed by the `AuthUser` model). This was historically in a separate `openneo_id` database (a legacy from when "OpenNeo ID" was envisioned as a service to unify auth across multiple OpenNeo projects). As of November 2025, the databases have been consolidated into a single database for simplicity.
**Implication**: Rails is configured for multi-database mode. User auth models live in `auth_user.rb` and connect to `openneo_id`.
User accounts are split across two related tables:
- `auth_users` - Authentication data (passwords, email, OAuth connections) via Devise
- `users` - Application data (points, closet settings, etc.)
These are linked via `User.remote_id``AuthUser.id`. See `db/openneo_id_migrate/README.md` for the historical context.
### Rails/React Hybrid

View file

@ -1,5 +0,0 @@
class AuthRecord < ApplicationRecord
self.abstract_class = true
connects_to database: {reading: :openneo_id, writing: :openneo_id}
end

View file

@ -1,5 +1,7 @@
class AuthUser < AuthRecord
self.table_name = 'users'
class AuthUser < ApplicationRecord
# TODO: Consider merging with User model to eliminate the remote_id relationship
# and simplify the authentication architecture. This would involve combining the
# auth_users and users tables into a single table.
devise :database_authenticatable, :encryptable, :registerable, :validatable,
:rememberable, :trackable, :recoverable, :omniauthable,

View file

@ -3,6 +3,9 @@ class User < ApplicationRecord
PreviewTopContributorsCount = 3
# TODO: This relationship could be simplified by merging the auth_users and users
# tables. Currently User.remote_id points to AuthUser.id, but if the tables were
# merged, we could eliminate remote_id and auth_server_id entirely.
belongs_to :auth_user, foreign_key: :remote_id, inverse_of: :user
delegate :disconnect_neopass, :uses_neopass?, to: :auth_user

View file

@ -1,57 +1,28 @@
development:
primary:
adapter: mysql2
host: <%= ENV.fetch("DB_HOST", "localhost") %>
database: openneo_impress
username: root
pool: 5
encoding: utf8mb4
collation: utf8mb4_unicode_520_ci
variables:
sql_mode: TRADITIONAL
openneo_id:
adapter: mysql2
host: <%= ENV.fetch("DB_HOST", "localhost") %>
database: openneo_id
username: root
pool: 2
variables:
sql_mode: TRADITIONAL
migrations_paths: db/openneo_id_migrate
adapter: mysql2
host: <%= ENV.fetch("DB_HOST", "localhost") %>
database: openneo_impress
username: root
pool: 5
encoding: utf8mb4
collation: utf8mb4_unicode_520_ci
variables:
sql_mode: TRADITIONAL
test:
primary:
adapter: mysql2
host: <%= ENV.fetch("DB_HOST", "localhost") %>
database: openneo_impress_test
username: root
pool: 5
encoding: utf8mb4
collation: utf8mb4_unicode_520_ci
variables:
sql_mode: TRADITIONAL
openneo_id:
adapter: mysql2
host: <%= ENV.fetch("DB_HOST", "localhost") %>
database: openneo_id_test
username: root
pool: 2
variables:
sql_mode: TRADITIONAL
migrations_paths: db/openneo_id_migrate
adapter: mysql2
host: <%= ENV.fetch("DB_HOST", "localhost") %>
database: openneo_impress_test
username: root
pool: 5
encoding: utf8mb4
collation: utf8mb4_unicode_520_ci
variables:
sql_mode: TRADITIONAL
production:
primary:
url: <%= ENV['DATABASE_URL_PRIMARY'] %>
encoding: utf8mb4
collation: utf8mb4_unicode_520_ci
variables:
sql_mode: TRADITIONAL
openneo_id:
url: <%= ENV['DATABASE_URL_OPENNEO_ID'] %>
variables:
sql_mode: TRADITIONAL
migrations_paths: db/openneo_id_migrate
url: <%= ENV['DATABASE_URL_PRIMARY'] %>
encoding: utf8mb4
collation: utf8mb4_unicode_520_ci
variables:
sql_mode: TRADITIONAL

View file

@ -1,6 +1,8 @@
class IncreaseUsernameLength < ActiveRecord::Migration[7.1]
def change
# NOTE: This is paired with a migration to the `openneo_id` database, too!
# NOTE: This was originally paired with a migration to the legacy `openneo_id`
# database (see db/openneo_id_migrate/20240401124406_increase_username_length.rb).
# As of November 2025, the databases have been consolidated.
reversible do |direction|
direction.up {
change_column :users, :name, :string, limit: 30, null: false

View file

@ -0,0 +1,45 @@
class CopyAuthUsersTableToMainDatabase < ActiveRecord::Migration[8.0]
def up
# Create auth_users table in openneo_impress with same structure as openneo_id.users
# This preserves all IDs, data, and constraints from the source table.
create_table "auth_users", id: { type: :integer, unsigned: true }, charset: "utf8mb3", collation: "utf8mb3_general_ci", force: :cascade do |t|
t.string "name", limit: 30, null: false
t.string "encrypted_password", limit: 64
t.string "email", limit: 50
t.string "password_salt", limit: 32
t.string "reset_password_token"
t.integer "sign_in_count", default: 0
t.datetime "current_sign_in_at", precision: nil
t.datetime "last_sign_in_at", precision: nil
t.string "current_sign_in_ip"
t.string "last_sign_in_ip"
t.integer "failed_attempts", default: 0
t.string "unlock_token"
t.datetime "locked_at", precision: nil
t.datetime "created_at", precision: nil
t.datetime "updated_at", precision: nil
t.datetime "reset_password_sent_at", precision: nil
t.datetime "remember_created_at"
t.string "provider"
t.string "uid"
t.string "neopass_email"
end
# Add indexes (matching openneo_id.users schema)
add_index "auth_users", ["email"], name: "index_auth_users_on_email", unique: true
add_index "auth_users", ["provider", "uid"], name: "index_auth_users_on_provider_and_uid", unique: true
add_index "auth_users", ["reset_password_token"], name: "index_auth_users_on_reset_password_token", unique: true
add_index "auth_users", ["unlock_token"], name: "index_auth_users_on_unlock_token", unique: true
# Copy all data from openneo_id.users to openneo_impress.auth_users
# This preserves all IDs so that User.remote_id continues to reference the correct AuthUser
execute <<-SQL
INSERT INTO openneo_impress.auth_users
SELECT * FROM openneo_id.users
SQL
end
def down
drop_table "auth_users"
end
end

View file

@ -0,0 +1,35 @@
# Legacy openneo_id Database Migrations
These migrations are kept for historical reference only. They were applied to the separate `openneo_id` database before it was consolidated into the main `openneo_impress` database in November 2025.
## What happened?
Originally, Dress to Impress used two separate MySQL databases:
- `openneo_impress` - Main application data (items, outfits, closets, etc.)
- `openneo_id` - Authentication data (user accounts, passwords, OAuth)
This split was a legacy from when "OpenNeo ID" was envisioned as a separate authentication service that would unify login across multiple OpenNeo projects. Since DTI was the only successful project, we consolidated the databases.
## Migration details
On **November 2, 2025**, the `openneo_id.users` table was copied to `openneo_impress.auth_users`, preserving all data and IDs. The `openneo_id` database was then removed from production.
See the main migrations directory for:
- `20251102064247_copy_auth_users_table_to_main_database.rb` - The migration that copied the data
## Can these migrations be run?
**No.** These migrations reference the `openneo_id` database which no longer exists. They are preserved purely as documentation of how the authentication schema evolved over time.
## Migration history
1. `20230807005748_add_remember_created_at_to_users.rb` - Added Devise rememberable feature
2. `20240313200849_add_omniauth_fields_to_users.rb` - Added NeoPass OAuth support
3. `20240315020053_allow_null_email_and_password_for_users.rb` - Made email/password optional for OAuth users
4. `20240401124406_increase_username_length.rb` - Increased username limit from 20 to 30 chars
5. `20240407135246_add_neo_pass_email_to_users.rb` - Added neopass_email field
6. `20240408120359_add_unique_index_for_omniauth_to_users.rb` - Added unique constraint for provider+uid
---
For current authentication schema, see `db/schema.rb` and look for the `auth_users` table.

View file

@ -1,40 +0,0 @@
# This file is auto-generated from the current state of the database. Instead
# of editing this file, please use the migrations feature of Active Record to
# incrementally modify your database, and then regenerate this schema definition.
#
# This file is the source Rails uses to define your schema when running `bin/rails
# db:schema:load`. When creating a new database, `bin/rails db:schema:load` tends to
# be faster and is potentially less error prone than running all of your
# migrations from scratch. Old migrations may fail to apply correctly if those
# migrations use external dependencies or application code.
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema[8.0].define(version: 2024_04_08_120359) do
create_table "users", id: { type: :integer, unsigned: true }, charset: "utf8mb3", collation: "utf8mb3_general_ci", force: :cascade do |t|
t.string "name", limit: 30, null: false
t.string "encrypted_password", limit: 64
t.string "email", limit: 50
t.string "password_salt", limit: 32
t.string "reset_password_token"
t.integer "sign_in_count", default: 0
t.datetime "current_sign_in_at", precision: nil
t.datetime "last_sign_in_at", precision: nil
t.string "current_sign_in_ip"
t.string "last_sign_in_ip"
t.integer "failed_attempts", default: 0
t.string "unlock_token"
t.datetime "locked_at", precision: nil
t.datetime "created_at", precision: nil
t.datetime "updated_at", precision: nil
t.datetime "reset_password_sent_at", precision: nil
t.datetime "remember_created_at"
t.string "provider"
t.string "uid"
t.string "neopass_email"
t.index ["email"], name: "index_users_on_email", unique: true
t.index ["provider", "uid"], name: "index_users_on_provider_and_uid", unique: true
t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
t.index ["unlock_token"], name: "index_users_on_unlock_token", unique: true
end
end

View file

@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema[8.0].define(version: 2025_02_16_041650) do
ActiveRecord::Schema[8.0].define(version: 2025_11_02_064247) do
create_table "alt_styles", charset: "utf8mb4", collation: "utf8mb4_unicode_520_ci", force: :cascade do |t|
t.integer "species_id", null: false
t.integer "color_id", null: false
@ -32,6 +32,33 @@ ActiveRecord::Schema[8.0].define(version: 2025_02_16_041650) do
t.string "secret", limit: 64, null: false
end
create_table "auth_users", id: { type: :integer, unsigned: true }, charset: "utf8mb3", collation: "utf8mb3_general_ci", force: :cascade do |t|
t.string "name", limit: 30, null: false
t.string "encrypted_password", limit: 64
t.string "email", limit: 50
t.string "password_salt", limit: 32
t.string "reset_password_token"
t.integer "sign_in_count", default: 0
t.datetime "current_sign_in_at", precision: nil
t.datetime "last_sign_in_at", precision: nil
t.string "current_sign_in_ip"
t.string "last_sign_in_ip"
t.integer "failed_attempts", default: 0
t.string "unlock_token"
t.datetime "locked_at", precision: nil
t.datetime "created_at", precision: nil
t.datetime "updated_at", precision: nil
t.datetime "reset_password_sent_at", precision: nil
t.datetime "remember_created_at"
t.string "provider"
t.string "uid"
t.string "neopass_email"
t.index ["email"], name: "index_auth_users_on_email", unique: true
t.index ["provider", "uid"], name: "index_auth_users_on_provider_and_uid", unique: true
t.index ["reset_password_token"], name: "index_auth_users_on_reset_password_token", unique: true
t.index ["unlock_token"], name: "index_auth_users_on_unlock_token", unique: true
end
create_table "campaigns", id: :integer, charset: "utf8mb4", collation: "utf8mb4_unicode_520_ci", force: :cascade do |t|
t.integer "progress", default: 0, null: false
t.integer "goal", null: false

View file

@ -419,19 +419,18 @@
community.mysql.mysql_db:
name:
- openneo_impress
- openneo_id
- name: Create MySQL user openneo_impress
community.mysql.mysql_user:
name: openneo_impress
password: "{{ mysql_user_password }}"
priv: "openneo_impress.*:ALL,openneo_id.*:ALL"
priv: "openneo_impress.*:ALL"
- name: Create MySQL user impress2020
community.mysql.mysql_user:
name: impress2020
password: "{{ mysql_user_password_2020 }}"
priv: "openneo_impress.*:ALL,openneo_id.*:ALL"
priv: "openneo_impress.*:ALL"
- name: Create the Neopets Media Archive data directory
file:

View file

@ -0,0 +1,287 @@
# Database Consolidation Deployment Guide
This document outlines the plan and checklist for consolidating the `openneo_id` database into the main `openneo_impress` database.
## Current Status: BLOCKED
**This migration cannot be deployed until Impress 2020 is retired.**
## The Problem
While the main DTI Rails app is ready to move to a single-database architecture, **Impress 2020 still directly accesses both databases**:
- `openneo_impress` - For reading item, pet, and outfit data
- `openneo_id` - For user authentication via GraphQL
If we consolidate the databases now, Impress 2020's authentication will break immediately, causing login failures for users accessing DTI through the Impress 2020 GraphQL API.
## Path Forward
There are two options to unblock this migration:
### Option A: Retire Impress 2020 First (Recommended)
1. Complete the migration of remaining Impress 2020 dependencies back to the main Rails app
- See `docs/impress-2020-dependencies.md` for current status
- Primary remaining dependencies: GraphQL API for outfit data, image generation service
2. Spin down the Impress 2020 service entirely
3. Execute the database consolidation (steps below)
### Option B: Coordinated Update (Complex)
1. Update Impress 2020 to point to `openneo_impress.auth_users` instead of `openneo_id.users`
2. Deploy both applications simultaneously during a maintenance window
3. Execute the database consolidation
**Recommendation:** Option A is simpler and aligns with our long-term goal of fully consolidating back into the Rails monolith.
---
## Deployment Checklist (When Ready)
⚠️ **DO NOT EXECUTE UNTIL IMPRESS 2020 IS RETIRED**
### Prerequisites
- [ ] Impress 2020 service is spun down and no longer accessing databases
- [ ] All Impress 2020 dependencies have been migrated to main Rails app
- [ ] Database backups are current and tested
- [ ] Maintenance window scheduled (estimate: 30-60 minutes)
### Phase 1: Deploy Write Lock
**Branch:** `feature/consolidate-auth-database` @ commit `604a8667`
**Purpose:** Prevent writes to AuthUser table while keeping login/logout functional.
**Steps:**
1. Deploy Phase 1 to production
2. Verify:
- [ ] Existing users can log in
- [ ] Existing users can log out
- [ ] Registration shows maintenance message
- [ ] Settings updates show maintenance message
- [ ] NeoPass connection shows maintenance message
**Expected Downtime:** None (read-only mode for account changes only)
### Phase 2: Copy Data
**Purpose:** Copy auth data from `openneo_id` to `openneo_impress` while table is stable.
**Steps:**
1. **Backup openneo_id database:**
```bash
mysqldump -h [host] -u [user] -p openneo_id > openneo_id_backup_$(date +%Y%m%d_%H%M%S).sql
```
2. **Verify backup:**
```bash
# Check file size is reasonable
ls -lh openneo_id_backup_*.sql
# Spot-check contents
head -n 50 openneo_id_backup_*.sql
```
3. **Run the migration:**
```bash
cd /var/www/impress
bundle exec rails db:migrate
```
4. **Verify data copy:**
```sql
-- Connect to MySQL
mysql -h [host] -u [user] -p
-- Check row counts match
SELECT COUNT(*) AS openneo_id_count FROM openneo_id.users;
SELECT COUNT(*) AS auth_users_count FROM openneo_impress.auth_users;
-- Spot-check a few records
SELECT id, name, email FROM openneo_id.users LIMIT 5;
SELECT id, name, email FROM openneo_impress.auth_users WHERE id IN (1, 2, 3, 4, 5);
-- Verify indexes were created
SHOW INDEX FROM openneo_impress.auth_users;
```
5. **Verify results:**
- [ ] Row counts match exactly
- [ ] Sample records match (IDs, names, emails)
- [ ] All 4 indexes created (email, provider+uid, reset_password_token, unlock_token)
**Expected Downtime:** None (still in write-lock mode)
### Phase 3: Switch to New Table
**Branch:** `feature/consolidate-auth-database` @ commit `2c21269a`
**Purpose:** Point AuthUser at consolidated table, restore full functionality.
**Steps:**
1. Deploy Phase 2 to production
2. **Immediately test critical paths:**
- [ ] Login with existing account
- [ ] Logout
- [ ] Register new account
- [ ] Update account settings (email, password)
- [ ] Connect NeoPass (if available)
- [ ] Disconnect NeoPass (if available)
3. **Monitor error logs:**
```bash
tail -f /var/www/impress/log/production.log | grep -i error
```
4. **Verify database queries are using auth_users:**
```bash
# Check recent queries in logs
grep "auth_users" /var/www/impress/log/production.log | tail -n 20
# Should see SELECT/INSERT/UPDATE on auth_users, NOT openneo_id.users
```
**Expected Downtime:** Brief (< 1 minute for deployment)
**Rollback Plan:** If critical issues found, revert to Phase 1 commit and restore openneo_id from backup.
### Phase 4: Documentation Update
**Branch:** `feature/consolidate-auth-database` @ commit `9ba94f9f`
**Purpose:** Update documentation to reflect single-database architecture.
**Steps:**
1. Deploy Phase 3 to production
2. Verify no errors
**Expected Downtime:** None
### Phase 5: Database Teardown
**Purpose:** Remove the now-unused `openneo_id` database.
**Steps:**
1. **Wait 7 days** to ensure no issues found in production
2. **Final backup:**
```bash
mysqldump -h [host] -u [user] -p openneo_id > openneo_id_final_backup_$(date +%Y%m%d_%H%M%S).sql
```
3. **Store backup offsite:**
- Upload to secure backup storage
- Keep for at least 90 days
4. **Drop the database:**
```sql
DROP DATABASE openneo_id;
```
5. **Remove environment variable:**
- Delete `DATABASE_URL_OPENNEO_ID` from production environment config
- Restart app to ensure it doesn't try to connect
6. **Update MySQL users:**
```sql
-- Remove openneo_id privileges from users
-- (Already done in deploy/setup.yml for new deployments)
```
**Expected Downtime:** None
---
## Rollback Procedures
### If Issues Found After Phase 3
1. **Immediate rollback:**
```bash
# Revert to Phase 1 commit
git checkout 604a8667
bundle exec rails db:migrate:down VERSION=20251102064247
# Deploy
```
2. **Restore openneo_id (if needed):**
```bash
mysql -h [host] -u [user] -p openneo_id < openneo_id_backup_[timestamp].sql
```
3. **Investigate issues before reattempting**
### If Data Corruption Detected
1. **Immediately restore from backup:**
```bash
# Drop corrupted auth_users table
mysql -h [host] -u [user] -p -e "DROP TABLE openneo_impress.auth_users;"
# Restore openneo_id if needed
mysql -h [host] -u [user] -p openneo_id < openneo_id_backup_[timestamp].sql
```
2. **Revert to pre-migration code**
3. **Review migration SQL before reattempting**
---
## Key Risks & Mitigations
| Risk | Impact | Mitigation | Status |
|------|--------|------------|--------|
| Impress 2020 auth breaks | HIGH - Users can't log in via I2020 | Block deployment until I2020 retired | ⚠️ BLOCKING |
| Data copy fails mid-migration | HIGH - Incomplete auth data | Wrapped in transaction, can rollback | ✅ Mitigated |
| Production traffic during copy | MEDIUM - Stale data | Write lock prevents changes | ✅ Mitigated |
| Schema mismatch between DBs | MEDIUM - Migration fails | Migration matches exact schema | ✅ Mitigated |
| Indexes not created | MEDIUM - Slow queries | Verification step checks indexes | ✅ Mitigated |
| Login tracking data loss | LOW - Missing login stats | Acceptable trade-off | ✅ Accepted |
---
## Success Criteria
- [ ] All existing users can log in
- [ ] New user registration works
- [ ] Settings updates work
- [ ] NeoPass connection/disconnection works
- [ ] No errors in production logs
- [ ] Query performance unchanged
- [ ] Database row counts match
- [ ] All auth_users indexes present
---
## Timeline Estimate
**Total time:** 30-60 minutes (after Impress 2020 retired)
- Phase 1 deployment: 5 min
- Phase 2 data copy: 5-10 min (depending on user count)
- Phase 3 deployment + testing: 15-30 min
- Phase 4 deployment: 5 min
- Phase 5 teardown: 7+ days later, 10 min
---
## Questions Before Proceeding
1. **Is Impress 2020 fully retired?** If not, STOP.
2. Do we have recent database backups? (< 24 hours old)
3. Do we have a maintenance window scheduled?
4. Have we announced the maintenance to users?
5. Do we have rollback access ready?
---
**Last Updated:** November 2025
**Status:** Blocked on Impress 2020 retirement
**Branch:** `feature/consolidate-auth-database`