feat(creator): Refactor Creator flow, implement book creation pipeline & versioning, and setup Docker staging
- Relocate dashboard routing to /creator and editor workspace to /creator/edit/{BookId}
- Implement CreateBookCommand and handler with transactional default chapter seeding
- Implement PublishBookVersionCommand and GetCreatorDashboardDataQuery
- Build CreatorDashboard modal and UI components with customized dark input styles
- Add run-stage.sh script to automate staging environment setup, database migrations, and health checks
- Update developer workflow rules in GEMINI.md
This commit is contained in:
Executable
+103
@@ -0,0 +1,103 @@
|
||||
#!/usr/bin/env bash
|
||||
# -------------------------------------------------------------
|
||||
# Staging Deploy & Orchestration Helper for NexusReader
|
||||
# -------------------------------------------------------------
|
||||
set -e
|
||||
|
||||
ENV_FILE=".env.stage"
|
||||
TEMPLATE_FILE=".env.stage.template"
|
||||
COMPOSE_FILE="docker-compose.stage.yml"
|
||||
|
||||
echo "🏁 Starting staging environment orchestration..."
|
||||
|
||||
# 1. Create .env.stage if it doesn't exist
|
||||
if [ ! -f "$ENV_FILE" ]; then
|
||||
if [ -f "$TEMPLATE_FILE" ]; then
|
||||
echo "📄 Creating $ENV_FILE from $TEMPLATE_FILE..."
|
||||
cp "$TEMPLATE_FILE" "$ENV_FILE"
|
||||
else
|
||||
echo "❌ Error: Template file $TEMPLATE_FILE not found."
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# 2. Check and generate secure random passwords for placeholders
|
||||
if grep -q "CHANGE_ME_TO_STRONG_PASSWORD" "$ENV_FILE"; then
|
||||
echo "🔐 Generating secure random passwords in $ENV_FILE..."
|
||||
PG_PASS=$(openssl rand -hex 16)
|
||||
NEO_PASS=$(openssl rand -hex 16)
|
||||
# Use standard sed compatible with Linux
|
||||
sed -i "s/POSTGRES_PASSWORD=CHANGE_ME_TO_STRONG_PASSWORD/POSTGRES_PASSWORD=$PG_PASS/g" "$ENV_FILE"
|
||||
sed -i "s/NEO4J_PASSWORD=CHANGE_ME_TO_STRONG_PASSWORD/NEO4J_PASSWORD=$NEO_PASS/g" "$ENV_FILE"
|
||||
fi
|
||||
|
||||
if grep -q "CHANGE_ME_TO_SECURE_ADMIN_PASSWORD" "$ENV_FILE"; then
|
||||
echo "🔐 Generating secure admin seed password in $ENV_FILE..."
|
||||
ADMIN_PASS=$(openssl rand -hex 16)
|
||||
sed -i "s/NEXUS_ADMIN_PASSWORD=CHANGE_ME_TO_SECURE_ADMIN_PASSWORD/NEXUS_ADMIN_PASSWORD=$ADMIN_PASS/g" "$ENV_FILE"
|
||||
fi
|
||||
|
||||
# Load staging variables for local execution context (needed for ports/migrations)
|
||||
# Clean up carriage returns just in case
|
||||
POSTGRES_USER=$(grep "^POSTGRES_USER=" "$ENV_FILE" | cut -d'=' -f2- | tr -d '\r')
|
||||
POSTGRES_PASSWORD=$(grep "^POSTGRES_PASSWORD=" "$ENV_FILE" | cut -d'=' -f2- | tr -d '\r')
|
||||
POSTGRES_DB=$(grep "^POSTGRES_DB=" "$ENV_FILE" | cut -d'=' -f2- | tr -d '\r')
|
||||
POSTGRES_PORT=$(grep "^POSTGRES_PORT=" "$ENV_FILE" | cut -d'=' -f2- | tr -d '\r')
|
||||
WEB_PORT=$(grep "^WEB_PORT=" "$ENV_FILE" | cut -d'=' -f2- | tr -d '\r')
|
||||
QDRANT_HTTP_PORT=$(grep "^QDRANT_HTTP_PORT=" "$ENV_FILE" | cut -d'=' -f2- | tr -d '\r')
|
||||
NEO4J_HTTP_PORT=$(grep "^NEO4J_HTTP_PORT=" "$ENV_FILE" | cut -d'=' -f2- | tr -d '\r')
|
||||
|
||||
# Fallbacks in case env parsing is empty
|
||||
POSTGRES_PORT=${POSTGRES_PORT:-5438}
|
||||
WEB_PORT=${WEB_PORT:-5080}
|
||||
|
||||
# 3. Stop any conflicting Docker Compose environments
|
||||
echo "🧹 Stopping existing containers..."
|
||||
docker compose -f "$COMPOSE_FILE" --env-file "$ENV_FILE" down --remove-orphans || true
|
||||
docker compose down --remove-orphans 2>/dev/null || true
|
||||
|
||||
# 4. Build and start containers
|
||||
echo "🚀 Building and starting staging containers..."
|
||||
docker compose -f "$COMPOSE_FILE" --env-file "$ENV_FILE" up -d --build
|
||||
|
||||
# 5. Wait for Database to be healthy
|
||||
echo "⏳ Waiting for database (nexus-db-stage) to become healthy..."
|
||||
MAX_ATTEMPTS=30
|
||||
attempt=0
|
||||
until [ "$(docker inspect --format='{{json .State.Health.Status}}' nexus-db-stage 2>/dev/null)" == "\"healthy\"" ]; do
|
||||
sleep 2
|
||||
attempt=$((attempt + 1))
|
||||
if [ $attempt -ge $MAX_ATTEMPTS ]; then
|
||||
echo "❌ Timeout: Database container never became healthy."
|
||||
docker compose -f "$COMPOSE_FILE" --env-file "$ENV_FILE" logs db
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
echo "✅ Database is healthy!"
|
||||
|
||||
# 6. Apply Entity Framework migrations
|
||||
echo "🔄 Applying EF Core migrations to staging database on port $POSTGRES_PORT..."
|
||||
export ConnectionStrings__PostgresConnection="Host=127.0.0.1;Port=$POSTGRES_PORT;Database=$POSTGRES_DB;Username=$POSTGRES_USER;Password=$POSTGRES_PASSWORD"
|
||||
dotnet ef database update --project src/NexusReader.Data --startup-project src/NexusReader.Web --no-build
|
||||
|
||||
# 7. Wait for Web Application to respond
|
||||
echo "⏳ Waiting for Web Application to start on http://localhost:$WEB_PORT..."
|
||||
MAX_WEB_ATTEMPTS=20
|
||||
web_attempt=0
|
||||
until curl -s -f "http://localhost:$WEB_PORT" >/dev/null; do
|
||||
sleep 2
|
||||
web_attempt=$((web_attempt + 1))
|
||||
if [ $web_attempt -ge $MAX_WEB_ATTEMPTS ]; then
|
||||
echo "⚠️ Warning: Web app is not responding yet on http://localhost:$WEB_PORT, but let's check logs..."
|
||||
docker compose -f "$COMPOSE_FILE" --env-file "$ENV_FILE" logs web
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
echo "🎉 Staging environment is ready!"
|
||||
echo "--------------------------------------------------------"
|
||||
echo "🌐 Web Application: http://localhost:$WEB_PORT"
|
||||
echo "🗄️ PostgreSQL Port: $POSTGRES_PORT"
|
||||
echo "🔎 Neo4j Console: http://localhost:$NEO4J_HTTP_PORT"
|
||||
echo "📊 Qdrant Service: http://localhost:$QDRANT_HTTP_PORT"
|
||||
echo "--------------------------------------------------------"
|
||||
Reference in New Issue
Block a user