What a mess
This commit is contained in:
174
scripts/migrate_fix_telegram_id.py
Normal file
174
scripts/migrate_fix_telegram_id.py
Normal file
@@ -0,0 +1,174 @@
|
||||
"""
|
||||
Migration: Fix telegram_id to allow NULL for web users
|
||||
|
||||
Changes:
|
||||
- Drop existing primary key constraint on telegram_id
|
||||
- Make telegram_id nullable
|
||||
- Ensure id column exists and is unique
|
||||
- Add constraint that either telegram_id OR username must be NOT NULL
|
||||
|
||||
Run this migration to allow web-only users without telegram_id.
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import os
|
||||
from sqlalchemy.ext.asyncio import create_async_engine
|
||||
from sqlalchemy import text
|
||||
|
||||
DB_USER = os.getenv("POSTGRES_USER")
|
||||
DB_PASS = os.getenv("POSTGRES_PASSWORD")
|
||||
DB_NAME = os.getenv("POSTGRES_DB")
|
||||
DB_HOST = os.getenv("POSTGRES_HOST", "localhost")
|
||||
DB_PORT = os.getenv("POSTGRES_PORT", "5432")
|
||||
|
||||
DATABASE_URL = f"postgresql+psycopg://{DB_USER}:{DB_PASS}@{DB_HOST}:{DB_PORT}/{DB_NAME}"
|
||||
|
||||
|
||||
async def migrate():
|
||||
engine = create_async_engine(DATABASE_URL)
|
||||
|
||||
async with engine.begin() as conn:
|
||||
print("Starting migration: Fix telegram_id to allow NULL...")
|
||||
|
||||
try:
|
||||
# First, check current state
|
||||
result = await conn.execute(text("""
|
||||
SELECT
|
||||
c.column_name,
|
||||
c.is_nullable,
|
||||
c.column_default
|
||||
FROM information_schema.columns c
|
||||
WHERE c.table_name = 'players'
|
||||
AND c.column_name IN ('telegram_id', 'id', 'username')
|
||||
ORDER BY c.ordinal_position
|
||||
"""))
|
||||
print("\nCurrent columns:")
|
||||
for row in result:
|
||||
print(f" - {row[0]}: nullable={row[1]}, default={row[2]}")
|
||||
|
||||
# Store foreign keys that reference players.telegram_id
|
||||
print("\nFinding foreign keys that reference players(telegram_id)...")
|
||||
result = await conn.execute(text("""
|
||||
SELECT
|
||||
tc.constraint_name,
|
||||
tc.table_name,
|
||||
kcu.column_name
|
||||
FROM information_schema.table_constraints tc
|
||||
JOIN information_schema.key_column_usage kcu
|
||||
ON tc.constraint_name = kcu.constraint_name
|
||||
JOIN information_schema.constraint_column_usage ccu
|
||||
ON tc.constraint_name = ccu.constraint_name
|
||||
WHERE tc.constraint_type = 'FOREIGN KEY'
|
||||
AND ccu.table_name = 'players'
|
||||
AND ccu.column_name = 'telegram_id'
|
||||
"""))
|
||||
fk_constraints = list(result)
|
||||
|
||||
# Drop foreign key constraints temporarily
|
||||
for fk_name, table_name, column_name in fk_constraints:
|
||||
print(f" Dropping FK: {table_name}.{fk_name}")
|
||||
await conn.execute(text(f"ALTER TABLE {table_name} DROP CONSTRAINT {fk_name}"))
|
||||
|
||||
# Check for primary key constraint
|
||||
result = await conn.execute(text("""
|
||||
SELECT constraint_name, constraint_type
|
||||
FROM information_schema.table_constraints
|
||||
WHERE table_name = 'players' AND constraint_type = 'PRIMARY KEY'
|
||||
"""))
|
||||
pk_constraints = list(result)
|
||||
|
||||
if pk_constraints:
|
||||
pk_name = pk_constraints[0][0]
|
||||
print(f"\nDropping PRIMARY KEY constraint: {pk_name}")
|
||||
await conn.execute(text(f"ALTER TABLE players DROP CONSTRAINT {pk_name}"))
|
||||
else:
|
||||
print("\n✓ No PRIMARY KEY constraint found")
|
||||
|
||||
# Make telegram_id nullable
|
||||
print("Making telegram_id nullable...")
|
||||
await conn.execute(text("""
|
||||
ALTER TABLE players
|
||||
ALTER COLUMN telegram_id DROP NOT NULL
|
||||
"""))
|
||||
|
||||
# Make telegram_id unique if not already
|
||||
print("Ensuring telegram_id is unique...")
|
||||
try:
|
||||
await conn.execute(text("""
|
||||
ALTER TABLE players
|
||||
ADD CONSTRAINT players_telegram_id_unique UNIQUE (telegram_id)
|
||||
"""))
|
||||
except Exception as e:
|
||||
if "already exists" in str(e):
|
||||
print("✓ telegram_id unique constraint already exists")
|
||||
else:
|
||||
raise
|
||||
|
||||
# Ensure id column exists and is unique
|
||||
result = await conn.execute(text("""
|
||||
SELECT column_name
|
||||
FROM information_schema.columns
|
||||
WHERE table_name='players' AND column_name='id'
|
||||
"""))
|
||||
if not list(result):
|
||||
print("Adding id column...")
|
||||
await conn.execute(text("""
|
||||
ALTER TABLE players
|
||||
ADD COLUMN id SERIAL UNIQUE
|
||||
"""))
|
||||
else:
|
||||
print("✓ id column exists")
|
||||
|
||||
# Make sure it's unique
|
||||
try:
|
||||
await conn.execute(text("""
|
||||
ALTER TABLE players
|
||||
ADD CONSTRAINT players_id_unique UNIQUE (id)
|
||||
"""))
|
||||
except Exception as e:
|
||||
if "already exists" in str(e):
|
||||
print("✓ id unique constraint already exists")
|
||||
else:
|
||||
raise
|
||||
|
||||
# Add check constraint that either telegram_id OR username must be NOT NULL
|
||||
print("Adding constraint: either telegram_id OR username must be NOT NULL...")
|
||||
try:
|
||||
await conn.execute(text("""
|
||||
ALTER TABLE players
|
||||
ADD CONSTRAINT players_id_check
|
||||
CHECK (telegram_id IS NOT NULL OR username IS NOT NULL)
|
||||
"""))
|
||||
except Exception as e:
|
||||
if "already exists" in str(e):
|
||||
print("✓ Check constraint already exists")
|
||||
else:
|
||||
raise
|
||||
|
||||
# Recreate foreign key constraints
|
||||
print("\nRecreating foreign key constraints...")
|
||||
for fk_name, table_name, column_name in fk_constraints:
|
||||
print(f" Adding FK: {table_name}.{fk_name}")
|
||||
await conn.execute(text(f"""
|
||||
ALTER TABLE {table_name}
|
||||
ADD CONSTRAINT {fk_name}
|
||||
FOREIGN KEY ({column_name})
|
||||
REFERENCES players(telegram_id)
|
||||
ON DELETE CASCADE
|
||||
"""))
|
||||
|
||||
print("\n✅ Migration completed successfully!")
|
||||
print("\nNow players can be created with:")
|
||||
print(" - telegram_id (Telegram users)")
|
||||
print(" - username + password_hash (web users)")
|
||||
print(" - Both telegram_id is unique and id is unique")
|
||||
|
||||
except Exception as e:
|
||||
print(f"\n❌ Migration failed: {e}")
|
||||
raise
|
||||
|
||||
await engine.dispose()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(migrate())
|
||||
94
scripts/migrate_web_auth.py
Normal file
94
scripts/migrate_web_auth.py
Normal file
@@ -0,0 +1,94 @@
|
||||
"""
|
||||
Migration: Add web authentication support to players table
|
||||
|
||||
Adds:
|
||||
- username column (unique, nullable for existing Telegram-only accounts)
|
||||
- password_hash column (nullable for Telegram-only accounts)
|
||||
- id auto-increment column for web users (telegram_id becomes nullable)
|
||||
|
||||
Run this migration before starting the API service.
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import os
|
||||
from sqlalchemy.ext.asyncio import create_async_engine
|
||||
from sqlalchemy import text
|
||||
|
||||
DB_USER = os.getenv("POSTGRES_USER")
|
||||
DB_PASS = os.getenv("POSTGRES_PASSWORD")
|
||||
DB_NAME = os.getenv("POSTGRES_DB")
|
||||
DB_HOST = os.getenv("POSTGRES_HOST", "localhost")
|
||||
DB_PORT = os.getenv("POSTGRES_PORT", "5432")
|
||||
|
||||
DATABASE_URL = f"postgresql+psycopg://{DB_USER}:{DB_PASS}@{DB_HOST}:{DB_PORT}/{DB_NAME}"
|
||||
|
||||
|
||||
async def migrate():
|
||||
engine = create_async_engine(DATABASE_URL)
|
||||
|
||||
async with engine.begin() as conn:
|
||||
print("Starting migration: Add web authentication support...")
|
||||
|
||||
# Check if columns already exist
|
||||
result = await conn.execute(text("""
|
||||
SELECT column_name
|
||||
FROM information_schema.columns
|
||||
WHERE table_name='players' AND column_name IN ('id', 'username', 'password_hash')
|
||||
"""))
|
||||
existing_columns = {row[0] for row in result}
|
||||
|
||||
if 'id' not in existing_columns:
|
||||
print("Adding id column...")
|
||||
await conn.execute(text("""
|
||||
ALTER TABLE players
|
||||
ADD COLUMN id SERIAL UNIQUE
|
||||
"""))
|
||||
else:
|
||||
print("✓ id column already exists")
|
||||
|
||||
if 'username' not in existing_columns:
|
||||
print("Adding username column...")
|
||||
await conn.execute(text("""
|
||||
ALTER TABLE players
|
||||
ADD COLUMN username VARCHAR(50) UNIQUE
|
||||
"""))
|
||||
else:
|
||||
print("✓ username column already exists")
|
||||
|
||||
if 'password_hash' not in existing_columns:
|
||||
print("Adding password_hash column...")
|
||||
await conn.execute(text("""
|
||||
ALTER TABLE players
|
||||
ADD COLUMN password_hash VARCHAR(255)
|
||||
"""))
|
||||
else:
|
||||
print("✓ password_hash column already exists")
|
||||
|
||||
# Note: telegram_id stays as primary key for backwards compatibility
|
||||
# Web users will use the 'id' column instead
|
||||
print("✓ Keeping telegram_id as primary key for Telegram users")
|
||||
|
||||
# Add constraint: either telegram_id or username must be present
|
||||
print("Adding check constraint...")
|
||||
try:
|
||||
await conn.execute(text("""
|
||||
ALTER TABLE players
|
||||
ADD CONSTRAINT players_auth_check
|
||||
CHECK (telegram_id IS NOT NULL OR username IS NOT NULL)
|
||||
"""))
|
||||
print("✓ Check constraint added")
|
||||
except Exception as e:
|
||||
if "already exists" in str(e):
|
||||
print("✓ Check constraint already exists")
|
||||
else:
|
||||
raise
|
||||
|
||||
print("\n✅ Migration completed successfully!")
|
||||
print("\nNote: Telegram users will continue to use telegram_id as primary key")
|
||||
print(" Web users will use the auto-incrementing id column")
|
||||
|
||||
await engine.dispose()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(migrate())
|
||||
185
scripts/setup_pwa.sh
Executable file
185
scripts/setup_pwa.sh
Executable file
@@ -0,0 +1,185 @@
|
||||
#!/bin/bash
|
||||
|
||||
# PWA Setup Script for Echoes of the Ashes
|
||||
# This script helps set up the PWA for the first time
|
||||
|
||||
set -e # Exit on error
|
||||
|
||||
echo "=================================================="
|
||||
echo " Echoes of the Ashes - PWA Setup"
|
||||
echo "=================================================="
|
||||
echo ""
|
||||
|
||||
# Check if we're in the right directory
|
||||
if [ ! -f "docker-compose.yml" ]; then
|
||||
echo "❌ Error: docker-compose.yml not found!"
|
||||
echo "Please run this script from the project root directory."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "✅ Found docker-compose.yml"
|
||||
echo ""
|
||||
|
||||
# Step 1: Check .env file
|
||||
echo "📝 Step 1: Checking .env file..."
|
||||
if [ ! -f ".env" ]; then
|
||||
echo "❌ Error: .env file not found!"
|
||||
echo "Please create .env file with database credentials."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! grep -q "JWT_SECRET_KEY" .env; then
|
||||
echo "⚠️ Warning: JWT_SECRET_KEY not found in .env"
|
||||
echo "Generating a secure JWT secret key..."
|
||||
JWT_SECRET=$(openssl rand -hex 32)
|
||||
echo "" >> .env
|
||||
echo "# JWT Secret for API Authentication" >> .env
|
||||
echo "JWT_SECRET_KEY=$JWT_SECRET" >> .env
|
||||
echo "✅ Added JWT_SECRET_KEY to .env"
|
||||
else
|
||||
echo "✅ JWT_SECRET_KEY already configured"
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# Step 2: Install npm dependencies
|
||||
echo "📦 Step 2: Installing NPM dependencies..."
|
||||
if [ ! -d "pwa/node_modules" ]; then
|
||||
echo "Installing dependencies in pwa/ directory..."
|
||||
cd pwa
|
||||
npm install
|
||||
cd ..
|
||||
echo "✅ NPM dependencies installed"
|
||||
else
|
||||
echo "✅ NPM dependencies already installed"
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# Step 3: Check for PWA icons
|
||||
echo "🎨 Step 3: Checking PWA icons..."
|
||||
MISSING_ICONS=0
|
||||
for icon in pwa/public/pwa-192x192.png pwa/public/pwa-512x512.png pwa/public/apple-touch-icon.png pwa/public/favicon.ico; do
|
||||
if [ ! -f "$icon" ]; then
|
||||
echo "⚠️ Missing: $icon"
|
||||
MISSING_ICONS=1
|
||||
fi
|
||||
done
|
||||
|
||||
if [ $MISSING_ICONS -eq 1 ]; then
|
||||
echo ""
|
||||
echo "⚠️ Warning: Some PWA icons are missing."
|
||||
echo "Creating placeholder icons..."
|
||||
|
||||
# Create simple placeholder icons using ImageMagick if available
|
||||
if command -v convert &> /dev/null; then
|
||||
echo "Using ImageMagick to create placeholder icons..."
|
||||
cd pwa/public
|
||||
|
||||
# Create 192x192 icon
|
||||
convert -size 192x192 xc:#646cff -font DejaVu-Sans-Bold -pointsize 72 \
|
||||
-fill white -gravity center -annotate +0+0 'E' pwa-192x192.png 2>/dev/null || true
|
||||
|
||||
# Create 512x512 icon
|
||||
convert -size 512x512 xc:#646cff -font DejaVu-Sans-Bold -pointsize 200 \
|
||||
-fill white -gravity center -annotate +0+0 'E' pwa-512x512.png 2>/dev/null || true
|
||||
|
||||
# Create apple touch icon
|
||||
convert -size 180x180 xc:#646cff -font DejaVu-Sans-Bold -pointsize 72 \
|
||||
-fill white -gravity center -annotate +0+0 'E' apple-touch-icon.png 2>/dev/null || true
|
||||
|
||||
cd ../..
|
||||
echo "✅ Created placeholder icons"
|
||||
else
|
||||
echo "⚠️ ImageMagick not found. Please create icons manually."
|
||||
echo "See pwa/public/README.md for instructions."
|
||||
fi
|
||||
else
|
||||
echo "✅ All PWA icons present"
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# Step 4: Run database migration
|
||||
echo "🗄️ Step 4: Running database migration..."
|
||||
echo "This adds web authentication support to the players table."
|
||||
read -p "Do you want to run the migration now? (y/n) " -n 1 -r
|
||||
echo
|
||||
if [[ $REPLY =~ ^[Yy]$ ]]; then
|
||||
if docker ps | grep -q echoes_of_the_ashes_bot; then
|
||||
docker exec -it echoes_of_the_ashes_bot python migrate_web_auth.py
|
||||
echo "✅ Migration completed"
|
||||
else
|
||||
echo "⚠️ Bot container not running. You'll need to run migration manually:"
|
||||
echo " docker exec -it echoes_of_the_ashes_bot python migrate_web_auth.py"
|
||||
fi
|
||||
else
|
||||
echo "⚠️ Skipping migration. Remember to run it before starting the API!"
|
||||
echo " docker exec -it echoes_of_the_ashes_bot python migrate_web_auth.py"
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# Step 5: Build and start services
|
||||
echo "🐳 Step 5: Building and starting Docker services..."
|
||||
read -p "Do you want to build and start the PWA services now? (y/n) " -n 1 -r
|
||||
echo
|
||||
if [[ $REPLY =~ ^[Yy]$ ]]; then
|
||||
echo "Building API and PWA containers..."
|
||||
docker-compose up -d --build echoes_of_the_ashes_api echoes_of_the_ashes_pwa
|
||||
echo "✅ Services started"
|
||||
echo ""
|
||||
echo "Waiting for services to initialize (10 seconds)..."
|
||||
sleep 10
|
||||
|
||||
# Check service health
|
||||
echo ""
|
||||
echo "🔍 Checking service health..."
|
||||
|
||||
if docker ps | grep -q echoes_of_the_ashes_api; then
|
||||
echo "✅ API container is running"
|
||||
else
|
||||
echo "❌ API container is not running!"
|
||||
echo "Check logs: docker logs echoes_of_the_ashes_api"
|
||||
fi
|
||||
|
||||
if docker ps | grep -q echoes_of_the_ashes_pwa; then
|
||||
echo "✅ PWA container is running"
|
||||
else
|
||||
echo "❌ PWA container is not running!"
|
||||
echo "Check logs: docker logs echoes_of_the_ashes_pwa"
|
||||
fi
|
||||
else
|
||||
echo "⚠️ Skipping Docker build. Build manually with:"
|
||||
echo " docker-compose up -d --build echoes_of_the_ashes_api echoes_of_the_ashes_pwa"
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# Summary
|
||||
echo "=================================================="
|
||||
echo " Setup Complete!"
|
||||
echo "=================================================="
|
||||
echo ""
|
||||
echo "📝 Next Steps:"
|
||||
echo ""
|
||||
echo "1. Verify services are running:"
|
||||
echo " docker ps | grep echoes"
|
||||
echo ""
|
||||
echo "2. Check logs for errors:"
|
||||
echo " docker logs echoes_of_the_ashes_api"
|
||||
echo " docker logs echoes_of_the_ashes_pwa"
|
||||
echo ""
|
||||
echo "3. Test the API:"
|
||||
echo " curl https://echoesoftheashgame.patacuack.net/api/"
|
||||
echo ""
|
||||
echo "4. Access the PWA:"
|
||||
echo " https://echoesoftheashgame.patacuack.net"
|
||||
echo ""
|
||||
echo "5. Test registration:"
|
||||
echo " curl -X POST https://echoesoftheashgame.patacuack.net/api/auth/register \\"
|
||||
echo " -H 'Content-Type: application/json' \\"
|
||||
echo " -d '{\"username\": \"testuser\", \"password\": \"testpass123\"}'"
|
||||
echo ""
|
||||
echo "📚 Documentation:"
|
||||
echo " - PWA_IMPLEMENTATION.md - Implementation summary"
|
||||
echo " - PWA_DEPLOYMENT.md - Deployment guide"
|
||||
echo " - pwa/README.md - PWA project overview"
|
||||
echo ""
|
||||
echo "🎮 Enjoy your new web-based game!"
|
||||
echo ""
|
||||
Reference in New Issue
Block a user