From 2e735f3b5a914b922cc12a3b064e846995f74d65 Mon Sep 17 00:00:00 2001 From: Tanya Date: Wed, 7 Jan 2026 14:05:13 -0500 Subject: [PATCH] chore: Add script to start all servers and update package.json This commit introduces a new script, `start_all.sh`, to facilitate the simultaneous startup of the backend, admin frontend, and viewer frontend servers. Additionally, the `package.json` file is updated to include a new command, `dev:all`, for executing this script. These changes enhance the development workflow by streamlining the server startup process. --- .gitea/workflows/ci.yml | 165 +++++++++++++++++++++++++++++++++++++++- package.json | 1 + start_all.sh | 87 +++++++++++++++++++++ 3 files changed, 252 insertions(+), 1 deletion(-) create mode 100755 start_all.sh diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml index d915797..e4787a0 100644 --- a/.gitea/workflows/ci.yml +++ b/.gitea/workflows/ci.yml @@ -196,7 +196,170 @@ jobs: run: | export PYTHONPATH=$(pwd) python -c "from backend.db.models import Base; from backend.db.session import engine; Base.metadata.create_all(bind=engine)" - python -c "from backend.worker import setup_auth_database_tables; setup_auth_database_tables()" + python << 'EOF' + # Initialize auth database schema without importing worker (avoids DeepFace/TensorFlow imports) + from backend.db.session import auth_engine + from sqlalchemy import text + + if auth_engine is None: + print("⚠️ Auth database not configured, skipping auth schema initialization") + else: + try: + print("🗃️ Setting up auth database tables...") + with auth_engine.connect() as conn: + # Create users table + conn.execute(text(""" + CREATE TABLE IF NOT EXISTS users ( + id SERIAL PRIMARY KEY, + email VARCHAR(255) UNIQUE NOT NULL, + name VARCHAR(255) NOT NULL, + password_hash VARCHAR(255) NOT NULL, + is_admin BOOLEAN DEFAULT FALSE, + has_write_access BOOLEAN DEFAULT FALSE, + email_verified BOOLEAN DEFAULT FALSE, + email_confirmation_token VARCHAR(255) UNIQUE, + email_confirmation_token_expiry TIMESTAMP, + password_reset_token VARCHAR(255) UNIQUE, + password_reset_token_expiry TIMESTAMP, + is_active BOOLEAN DEFAULT TRUE, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ); + """)) + + # Add missing columns if table already exists + for col_def in [ + "ALTER TABLE users ADD COLUMN IF NOT EXISTS is_admin BOOLEAN DEFAULT FALSE", + "ALTER TABLE users ADD COLUMN IF NOT EXISTS has_write_access BOOLEAN DEFAULT FALSE", + "ALTER TABLE users ADD COLUMN IF NOT EXISTS email_verified BOOLEAN DEFAULT FALSE", + "ALTER TABLE users ADD COLUMN IF NOT EXISTS email_confirmation_token VARCHAR(255)", + "ALTER TABLE users ADD COLUMN IF NOT EXISTS email_confirmation_token_expiry TIMESTAMP", + "ALTER TABLE users ADD COLUMN IF NOT EXISTS password_reset_token VARCHAR(255)", + "ALTER TABLE users ADD COLUMN IF NOT EXISTS password_reset_token_expiry TIMESTAMP", + "ALTER TABLE users ADD COLUMN IF NOT EXISTS is_active BOOLEAN DEFAULT TRUE", + ]: + try: + conn.execute(text(col_def)) + except Exception: + pass + + # Create unique indexes + conn.execute(text(""" + CREATE UNIQUE INDEX IF NOT EXISTS users_email_confirmation_token_key + ON users(email_confirmation_token) + WHERE email_confirmation_token IS NOT NULL; + """)) + conn.execute(text(""" + CREATE UNIQUE INDEX IF NOT EXISTS users_password_reset_token_key + ON users(password_reset_token) + WHERE password_reset_token IS NOT NULL; + """)) + + # Create pending_identifications table + conn.execute(text(""" + CREATE TABLE IF NOT EXISTS pending_identifications ( + id SERIAL PRIMARY KEY, + face_id INTEGER NOT NULL, + user_id INTEGER NOT NULL, + first_name VARCHAR(255) NOT NULL, + last_name VARCHAR(255) NOT NULL, + middle_name VARCHAR(255), + maiden_name VARCHAR(255), + date_of_birth DATE, + status VARCHAR(50) DEFAULT 'pending', + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE + ); + """)) + conn.execute(text("CREATE INDEX IF NOT EXISTS idx_pending_identifications_face_id ON pending_identifications(face_id);")) + conn.execute(text("CREATE INDEX IF NOT EXISTS idx_pending_identifications_user_id ON pending_identifications(user_id);")) + conn.execute(text("CREATE INDEX IF NOT EXISTS idx_pending_identifications_status ON pending_identifications(status);")) + + # Create pending_photos table + conn.execute(text(""" + CREATE TABLE IF NOT EXISTS pending_photos ( + id SERIAL PRIMARY KEY, + user_id INTEGER NOT NULL, + filename VARCHAR(255) NOT NULL, + original_filename VARCHAR(255) NOT NULL, + file_path VARCHAR(512) NOT NULL, + file_size INTEGER NOT NULL, + mime_type VARCHAR(100) NOT NULL, + status VARCHAR(50) DEFAULT 'pending', + submitted_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + reviewed_at TIMESTAMP, + reviewed_by INTEGER, + rejection_reason TEXT, + FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE + ); + """)) + conn.execute(text("CREATE INDEX IF NOT EXISTS idx_pending_photos_user_id ON pending_photos(user_id);")) + conn.execute(text("CREATE INDEX IF NOT EXISTS idx_pending_photos_status ON pending_photos(status);")) + conn.execute(text("CREATE INDEX IF NOT EXISTS idx_pending_photos_submitted_at ON pending_photos(submitted_at);")) + + # Create inappropriate_photo_reports table + conn.execute(text(""" + CREATE TABLE IF NOT EXISTS inappropriate_photo_reports ( + id SERIAL PRIMARY KEY, + photo_id INTEGER NOT NULL, + user_id INTEGER NOT NULL, + status VARCHAR(50) DEFAULT 'pending', + reported_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + reviewed_at TIMESTAMP, + reviewed_by INTEGER, + review_notes TEXT, + report_comment TEXT, + FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE, + UNIQUE(photo_id, user_id) + ); + """)) + conn.execute(text("CREATE INDEX IF NOT EXISTS idx_inappropriate_photo_reports_photo_id ON inappropriate_photo_reports(photo_id);")) + conn.execute(text("CREATE INDEX IF NOT EXISTS idx_inappropriate_photo_reports_user_id ON inappropriate_photo_reports(user_id);")) + conn.execute(text("CREATE INDEX IF NOT EXISTS idx_inappropriate_photo_reports_status ON inappropriate_photo_reports(status);")) + conn.execute(text("CREATE INDEX IF NOT EXISTS idx_inappropriate_photo_reports_reported_at ON inappropriate_photo_reports(reported_at);")) + + # Create pending_linkages table + conn.execute(text(""" + CREATE TABLE IF NOT EXISTS pending_linkages ( + id SERIAL PRIMARY KEY, + photo_id INTEGER NOT NULL, + tag_id INTEGER, + tag_name VARCHAR(255), + user_id INTEGER NOT NULL, + status VARCHAR(50) DEFAULT 'pending', + notes TEXT, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE + ); + """)) + conn.execute(text("CREATE INDEX IF NOT EXISTS idx_pending_linkages_photo_id ON pending_linkages(photo_id);")) + conn.execute(text("CREATE INDEX IF NOT EXISTS idx_pending_linkages_tag_id ON pending_linkages(tag_id);")) + conn.execute(text("CREATE INDEX IF NOT EXISTS idx_pending_linkages_user_id ON pending_linkages(user_id);")) + conn.execute(text("CREATE INDEX IF NOT EXISTS idx_pending_linkages_status ON pending_linkages(status);")) + + # Create photo_favorites table + conn.execute(text(""" + CREATE TABLE IF NOT EXISTS photo_favorites ( + id SERIAL PRIMARY KEY, + photo_id INTEGER NOT NULL, + user_id INTEGER NOT NULL, + favorited_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE, + UNIQUE(photo_id, user_id) + ); + """)) + conn.execute(text("CREATE INDEX IF NOT EXISTS idx_photo_favorites_photo_id ON photo_favorites(photo_id);")) + conn.execute(text("CREATE INDEX IF NOT EXISTS idx_photo_favorites_user_id ON photo_favorites(user_id);")) + conn.execute(text("CREATE INDEX IF NOT EXISTS idx_photo_favorites_favorited_at ON photo_favorites(favorited_at);")) + + conn.commit() + + print("✅ Auth database tables created/verified successfully") + except Exception as e: + print(f"⚠️ Failed to create auth database tables: {e}") + EOF echo "✅ Database schemas initialized (main and auth)" - name: Run backend tests diff --git a/package.json b/package.json index 92da120..17e122d 100644 --- a/package.json +++ b/package.json @@ -8,6 +8,7 @@ "dev:admin": "npm run dev --prefix admin-frontend", "dev:viewer": "npm run dev --prefix viewer-frontend", "dev:backend": "source venv/bin/activate && export PYTHONPATH=$(pwd) && uvicorn backend.app:app --host 127.0.0.1 --port 8000", + "dev:all": "./start_all.sh", "build:admin": "npm run build --prefix admin-frontend", "build:viewer": "npm run build --prefix viewer-frontend", "build:all": "npm run build:admin && npm run build:viewer", diff --git a/start_all.sh b/start_all.sh new file mode 100755 index 0000000..c38f691 --- /dev/null +++ b/start_all.sh @@ -0,0 +1,87 @@ +#!/bin/bash +# Start all three servers: backend, admin-frontend, and viewer-frontend + +set -euo pipefail + +cd "$(dirname "$0")" + +# Colors for output +GREEN='\033[0;32m' +BLUE='\033[0;34m' +YELLOW='\033[1;33m' +NC='\033[0m' # No Color + +echo -e "${BLUE}🚀 Starting all PunimTag servers...${NC}" +echo "" + +# Function to cleanup on exit +cleanup() { + echo "" + echo -e "${YELLOW}Shutting down all servers...${NC}" + kill $BACKEND_PID 2>/dev/null || true + kill $ADMIN_PID 2>/dev/null || true + kill $VIEWER_PID 2>/dev/null || true + exit +} + +trap cleanup SIGINT SIGTERM + +# Start backend +echo -e "${GREEN}📦 Starting backend server...${NC}" +# Use explicit Python path to avoid Cursor interception +PYTHON_BIN="/usr/bin/python3" +if [ ! -f "$PYTHON_BIN" ]; then + if command -v python3 >/dev/null 2>&1; then + PYTHON_BIN="$(which python3)" + elif command -v python >/dev/null 2>&1; then + PYTHON_BIN="$(which python)" + else + echo -e "${YELLOW}❌ Python3 not found${NC}" + exit 1 + fi +fi + +if [ -d "venv" ]; then + source venv/bin/activate +fi +export PYTHONPATH="$(pwd)" +"$PYTHON_BIN" -m uvicorn backend.app:app --host 127.0.0.1 --port 8000 --reload > /tmp/backend.log 2>&1 & +BACKEND_PID=$! + +# Wait a moment for backend to start +sleep 2 + +# Start admin-frontend +echo -e "${GREEN}📦 Starting admin-frontend...${NC}" +cd admin-frontend +npm run dev > /tmp/admin-frontend.log 2>&1 & +ADMIN_PID=$! +cd .. + +# Start viewer-frontend +echo -e "${GREEN}📦 Starting viewer-frontend...${NC}" +cd viewer-frontend +npm run dev > /tmp/viewer-frontend.log 2>&1 & +VIEWER_PID=$! +cd .. + +echo "" +echo -e "${GREEN}✅ All servers started!${NC}" +echo "" +echo -e "${BLUE}📍 Server URLs:${NC}" +echo -e " Backend API: ${GREEN}http://127.0.0.1:8000${NC}" +echo -e " API Docs: ${GREEN}http://127.0.0.1:8000/docs${NC}" +echo -e " Admin Frontend: ${GREEN}http://127.0.0.1:3000${NC}" +echo -e " Viewer Frontend: ${GREEN}http://127.0.0.1:3001${NC}" +echo "" +echo -e "${YELLOW}📋 Logs:${NC}" +echo -e " Backend: ${BLUE}/tmp/backend.log${NC}" +echo -e " Admin: ${BLUE}/tmp/admin-frontend.log${NC}" +echo -e " Viewer: ${BLUE}/tmp/viewer-frontend.log${NC}" +echo "" +echo -e "${YELLOW}Press Ctrl+C to stop all servers${NC}" +echo "" + +# Wait for all processes +wait +