AI Host Club - メールアドレス任意化改修記録

AI Host Club - メールアドレス任意化改修記録

📋 プロジェクト概要

地雷系テーマのAIホストクラブシステムにおいて、ユーザー登録時のメールアドレスを必須項目から任意項目に変更する改修を実施。将来の課金機能導入時にメールアドレスを必須化する設計も含めた、段階的なUX改善プロジェクト。

🎯 改修目的

ビジネス要件

  • 新規ユーザーの登録ハードル低減: メールアドレス入力の心理的障壁を排除
  • お試し利用の促進: 気軽に体験できる環境の提供
  • 将来の収益化準備: 有料プラン利用時のメールアドレス必須化への布石

技術要件

  • 既存ユーザーデータの完全保持
  • Dockerコンテナ環境との互換性維持
  • 自動マイグレーション機能の実装

🛠️ 技術スタック

  • Backend: Flask + SQLAlchemy
  • Database: SQLite (本番PostgreSQL対応)
  • Container: Docker + Docker Compose
  • Frontend: HTML5/CSS3 + JavaScript

📊 実装内容

1. データベースモデル変更

class User(UserMixin, db.Model):
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(80), unique=True, nullable=False)
    email = db.Column(db.String(120), unique=True, nullable=True)  # 必須→任意
    password_hash = db.Column(db.String(255), nullable=False)
    is_admin = db.Column(db.Boolean, default=False)
    created_at = db.Column(db.DateTime, default=datetime.utcnow)
    last_login = db.Column(db.DateTime)
    has_premium = db.Column(db.Boolean, default=False)  # 課金機能準備

主な変更点:
- emailフィールドをnullable=Trueに変更
- has_premiumフィールドを追加(将来の課金機能用)

2. 登録フォームUI改善

<div class=\"input-group\">
    <label for=\"email\" class=\"input-label\">📧 メールアドレス(任意)</label>
    <input 
        type=\"email\" 
        id=\"email\" 
        name=\"email\"
        class=\"form-input\" 
        placeholder=\"メールアドレスを入力(将来の有料機能利用時に必要)\"
    >
    <div style=\"color: #ff1493; font-size: 0.85rem; margin-top: 0.5rem;\">
        💌 今は入力不要です。有料プラン利用時に必要になります。
    </div>
</div>

UX改善:
- required属性の削除
- 「任意」表記の明記
- 将来の有料機能について説明追加

3. バックエンド処理の最適化

@app.route('/register', methods=['GET', 'POST'])
def register():
    if request.method == 'POST':
        username = request.form.get('username')
        email = request.form.get('email', '').strip()  # 空文字を許可
        password = request.form.get('password')

        # メールアドレスが入力されている場合のみ重複チェック
        if email and User.query.filter_by(email=email).first():
            flash('そのメールアドレスは既に登録されています。')
            return render_template('register.html')

        # Noneまたは空文字での登録を許可
        user = User(username=username, email=email if email else None)
        user.set_password(password)
        db.session.add(user)
        db.session.commit()

処理改善:
- 空のメールアドレス処理の最適化
- 重複チェックの条件分岐追加
- エラーハンドリングの強化

🔧 技術的課題と解決策

課題1: データベースパス管理の複雑化

問題: 開発環境・Docker環境で異なるデータベースファイルパスを参照

解決策: 環境変数による動的パス設定

# 環境変数でデータベースパスを制御
database_url = os.getenv('DATABASE_URL')
if not database_url:
    app_dir = os.path.dirname(os.path.abspath(__file__))
    instance_dir = os.path.join(app_dir, 'instance')
    os.makedirs(instance_dir, exist_ok=True)

    db_filename = os.getenv('DATABASE_FILENAME', 'ai_host_club.db')
    db_path = os.path.join(instance_dir, db_filename)
    database_url = f'sqlite:///{db_path}'

効果:
- 開発環境: instance/ai_host_club.db
- Docker環境: 環境変数で制御可能
- 本番環境: PostgreSQLなど外部DB対応

課題2: 本番環境での自動マイグレーション

問題: Docker再デプロイ時の既存データ移行

解決策: 起動時自動マイグレーション機能

def perform_auto_migration():
    \"\"\"自動マイグレーション実行\"\"\"
    if database_url.startswith('sqlite:///'):
        sqlite_path = database_url.replace('sqlite:///', '')
        if os.path.exists(sqlite_path):
            conn = sqlite3.connect(sqlite_path)
            cursor = conn.cursor()

            # スキーマチェック
            cursor.execute(\"PRAGMA table_info(user)\")
            columns = cursor.fetchall()
            column_names = [col[1] for col in columns]

            # 必要に応じてマイグレーション実行
            if 'has_premium' not in column_names:
                cursor.execute(\"ALTER TABLE user ADD COLUMN has_premium BOOLEAN DEFAULT 0\")

            # NOT NULL制約の削除
            if email_column_has_not_null_constraint:
                # テーブル再作成による制約変更

安全性確保:
- 既存データの完全保持
- トランザクション処理によるロールバック対応
- スキーマバージョン管理

🎨 プロフィール設定機能の追加

ユーザーが後からメールアドレスを設定できる機能を実装:

@app.route('/profile')
@login_required
def profile():
    return render_template('profile.html')

@app.route('/update_profile', methods=['POST'])
@login_required
def update_profile():
    email = request.form.get('email', '').strip()

    # 重複チェック(既存とは異なる場合のみ)
    if email and email != current_user.email:
        existing_user = User.query.filter_by(email=email).first()
        if existing_user:
            flash('そのメールアドレスは既に使用されています。')
            return redirect(url_for('profile'))

    current_user.email = email if email else None
    db.session.commit()

機能特徴:
- メールアドレスの追加・変更・削除
- パスワード変更機能
- 将来の課金ステータス表示準備

📦 Docker対応の強化

環境別設定ファイル

.env (開発環境):

# Database Configuration
# 開発環境では相対パス
DATABASE_FILENAME=ai_host_club.db

.env.docker (本番環境):

# Database Configuration for Docker
DATABASE_URL=sqlite:////app/data/ai_host_club.db
FLASK_ENV=production

docker-compose.yml最適化

services:
  ai-host-club:
    build: .
    volumes:
      - ./instance:/app/instance  # DB永続化
      - ./.env:/app/.env:ro       # 環境変数
    environment:
      - FLASK_ENV=production

📈 成果と効果

ユーザビリティ向上

  • 登録フォーム項目削減: 必須項目3つ→2つ
  • 心理的ハードル低減: メールアドレス入力の抵抗感排除
  • 段階的エンゲージメント: まず体験→必要時に詳細情報

技術的改善

  • 環境統一: 開発・Docker・本番での一貫したDB管理
  • 自動化: マイグレーション処理の無人化
  • 拡張性: 将来の課金機能への準備完了

開発効率化

  • デプロイ自動化: git pushdocker-compose up -dで完了
  • データ保護: 既存ユーザー情報の完全保持
  • 回帰テスト: 既存機能への影響ゼロ

🚀 今後の展開

短期計画

  • A/Bテスト実施(登録率の測定)
  • ユーザーフィードバック収集
  • プロフィール設定の利用状況分析

中期計画

  • 課金機能実装時のメールアドレス必須化
  • メール通知機能(有料ユーザー向け)
  • パスワードリセット機能

長期計画

  • マルチテナント対応
  • 外部認証連携(OAuth)
  • データ分析基盤の構築

📝 学び・ノウハウ

データベース設計

  • NOT NULL制約の変更: SQLiteでは新しいテーブル作成が必要
  • マイグレーション戦略: 段階的な構造変更とデータ保持
  • 環境差異対応: 開発・本番での一貫性確保

Flask開発

  • 環境変数活用: 設定の外部化による柔軟性向上
  • エラーハンドリング: ユーザビリティを損なわない例外処理
  • セキュリティ: 入力検証とSQLインジェクション対策

Docker運用

  • ボリュームマウント: データ永続化とホスト連携
  • 環境分離: 開発・本番での設定差異管理
  • ヘルスチェック: 自動復旧機能の実装

💡 推奨事項

類似プロジェクトへの適用

  1. 段階的な要求情報収集: 初回は最小限、必要時に詳細化
  2. 自動マイグレーション: 本番運用での安全なスキーマ変更
  3. 環境統一: 開発から本番まで一貫したデータ管理

設計思想

  • ユーザーファースト: 技術的制約よりユーザビリティ優先
  • 将来拡張性: 現在の要件と将来計画のバランス
  • 運用自動化: 人的ミスを排除する仕組み作り

本記事は、実際のWebアプリケーション改修プロジェクトの記録です。同様の課題を抱える開発者の参考になれば幸いです。

同様の開発をご希望ですか?

この事例と同様の開発やカスタマイズについて、
お気軽にご相談ください。

お問い合わせ