diff --git a/.github/workflows/cloudrun-deploy.yml b/.github/workflows/cloudrun-deploy.yml new file mode 100644 index 0000000..0f21427 --- /dev/null +++ b/.github/workflows/cloudrun-deploy.yml @@ -0,0 +1,64 @@ +name: Deploy to Cloud Run + +on: + workflow_dispatch: + push: + branches: + - main + paths: + - 'Backend/**' + - '.github/workflows/cloudrun-deploy.yml' + +jobs: + deploy: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.11' + + # Add explicit pip install step + - name: Install pip dependencies + working-directory: Backend + run: | + python -m pip install --upgrade pip + pip install -r requirements.dev.txt + + - id: auth + name: Authenticate with Google Cloud + uses: google-github-actions/auth@v2 + with: + credentials_json: '${{ secrets.GCP_SA_KEY }}' + token_format: 'access_token' + + - name: Set up Cloud SDK + uses: google-github-actions/setup-gcloud@v2 + with: + project_id: gohub-92b6b + + - name: Build and Push Container + env: + REGION: us-west1 + PROJECT_ID: gohub-92b6b + run: | + # Configure docker to use gcloud credentials + gcloud auth configure-docker us-west1-docker.pkg.dev + + # Build the container + docker build -t us-west1-docker.pkg.dev/$PROJECT_ID/cloud-run-source-deploy/ngo-connect-backend:${{ github.sha }} ./Backend + + # Push to Artifact Registry + docker push us-west1-docker.pkg.dev/$PROJECT_ID/cloud-run-source-deploy/ngo-connect-backend:${{ github.sha }} + + # Deploy to Cloud Run + gcloud run deploy ngo-connect-backend \ + --image us-west1-docker.pkg.dev/$PROJECT_ID/cloud-run-source-deploy/ngo-connect-backend:${{ github.sha }} \ + --region $REGION \ + --platform managed \ + --allow-unauthenticated \ + --set-env-vars "DATABASE_URL=${{ secrets.DATABASE_URL }},FLASK_APP=${{ secrets.FLASK_APP }},JWT_SECRET_KEY=${{ secrets.JWT_SECRET_KEY }}" \ No newline at end of file diff --git a/.github/workflows/cloudsql-migrate.yml b/.github/workflows/cloudsql-migrate.yml new file mode 100644 index 0000000..4a7018e --- /dev/null +++ b/.github/workflows/cloudsql-migrate.yml @@ -0,0 +1,148 @@ +name: Database Migrations + +on: + workflow_dispatch: + push: + branches: + - main + paths: + - 'Backend/migrations/**' + - 'Backend/app/models.py' # Path to match actual models location + - 'Backend/app/models/**' # Watch for any future model files in a models directory + - 'Backend/manage_db.py' + + - '.github/workflows/cloudsql-migrate.yml' + +jobs: + migrate: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.11' + cache: 'pip' + cache-dependency-path: 'Backend/requirements.dev.txt' + + - name: Setup GCP Credentials + run: | + echo '${{ secrets.GCP_SA_KEY }}' > gcp-credentials.json + chmod 600 gcp-credentials.json + + - name: Install and start Cloud SQL Proxy v2 + run: | + # Download and setup Cloud SQL Proxy + curl -o cloud-sql-proxy https://storage.googleapis.com/cloud-sql-connectors/cloud-sql-proxy/v2.8.1/cloud-sql-proxy.linux.amd64 + chmod +x cloud-sql-proxy + + ## Start proxy with structured logging + ./cloud-sql-proxy \ + --credentials-file=gcp-credentials.json \ + --address 0.0.0.0 \ + --port 5432 \ + --structured-logs \ + gohub-92b6b:us-west1:ngo-connect-db > proxy.log 2>&1 & + + # Wait for proxy to start and verify + sleep 10 + if ! netstat -tlpn | grep :5432 > /dev/null; then + echo "Error: Cloud SQL Proxy failed to start" + cat proxy.log + exit 1 + fi + echo "Cloud SQL Proxy started successfully" + + - name: Install dependencies + working-directory: Backend + run: | + python -m pip install --upgrade pip + pip install -r requirements.dev.txt + + - name: Create required directories + working-directory: Backend + run: | + mkdir -p migrations/versions + mkdir -p uploads + + - name: Setup environment variables + working-directory: Backend + run: | + # Create .env file with all required variables + cat << EOF > .env + FLASK_APP=app:create_app() + FLASK_ENV=production + K_SERVICE=cloud-run + USE_CLOUD_SQL_PROXY=true + DATABASE_URL=postgresql://postgres:${{ secrets.DB_PASSWORD }}@127.0.0.1:5432/ngo_connect + DB_PASSWORD=${{ secrets.DB_PASSWORD }} + GOOGLE_APPLICATION_CREDENTIALS=${{ github.workspace }}/gcp-credentials.json + GOOGLE_CLOUD_PROJECT=gohub-92b6b + JWT_SECRET_KEY=${{ secrets.JWT_SECRET_KEY }} + UPLOAD_FOLDER=${{ github.workspace }}/Backend/uploads + EOF + + # Export for immediate use + export $(cat .env | xargs) + export PYTHONPATH="${PWD}:${PYTHONPATH}" + + - name: Verify database connection + working-directory: Backend + run: | + python << EOF + import psycopg2 + import sys + import logging + + logging.basicConfig(level=logging.INFO) + logger = logging.getLogger() + + try: + logger.info("Testing database connection...") + conn = psycopg2.connect( + dbname='ngo_connect', + user='postgres', + password='${{ secrets.DB_PASSWORD }}', + host='127.0.0.1', + port='5432' + ) + with conn.cursor() as cur: + cur.execute('SELECT version();') + version = cur.fetchone() + logger.info(f"Connected to PostgreSQL: {version[0]}") + conn.close() + except Exception as e: + logger.error(f"Database connection failed: {e}") + sys.exit(1) + EOF + + - name: Run Database Migrations + working-directory: Backend + run: | + echo "Current directory: $(pwd)" + echo "Starting migrations..." + python manage_db.py + + - name: Store migration logs + if: always() + uses: actions/upload-artifact@v4 + with: + name: migration-logs + path: | + Backend/migration.log + proxy.log + retention-days: 7 + + - name: Stop Cloud SQL Proxy + if: always() + run: | + pkill cloud-sql-proxy || true + + - name: Cleanup + if: always() + run: | + rm -f gcp-credentials.json + rm -f Backend/.env \ No newline at end of file diff --git a/.github/workflows/firebase-hosting-merge.yml b/.github/workflows/firebase-hosting-merge.yml new file mode 100644 index 0000000..f0bf9c7 --- /dev/null +++ b/.github/workflows/firebase-hosting-merge.yml @@ -0,0 +1,50 @@ +# This file was auto-generated by the Firebase CLI +# https://github.com/firebase/firebase-tools + +name: Deploy to Firebase Hosting on merge +on: + push: + branches: + - main + +jobs: + build_and_deploy: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + # Debug step to verify paths + - name: Debug Paths + run: | + echo "Current directory: $(pwd)" + echo "Contents of Frontend directory:" + ls -la Frontend + + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: '20' + cache: 'npm' + cache-dependency-path: 'Frontend/package-lock.json' + + # Install dependencies and build + - name: Install and Build + working-directory: Frontend + run: | + echo "Installing dependencies..." + npm ci + echo "Building project..." + npm run build + env: + CI: false + REACT_APP_BASE_API_URL: ${{ secrets.REACT_APP_BASE_API_URL }} + + # Deploy to production + - name: Deploy to Firebase + uses: FirebaseExtended/action-hosting-deploy@v0 + with: + repoToken: ${{ secrets.GITHUB_TOKEN }} + firebaseServiceAccount: ${{ secrets.FIREBASE_SERVICE_ACCOUNT_GOHUB_92B6B }} + channelId: live + projectId: gohub-92b6b + entryPoint: './Frontend' \ No newline at end of file diff --git a/.github/workflows/firebase-hosting-pull-request.yml b/.github/workflows/firebase-hosting-pull-request.yml new file mode 100644 index 0000000..97a1442 --- /dev/null +++ b/.github/workflows/firebase-hosting-pull-request.yml @@ -0,0 +1,48 @@ +# This file was auto-generated by the Firebase CLI +# https://github.com/firebase/firebase-tools + +name: Deploy to Firebase Hosting on PR +on: + pull_request: + branches: + - main +defaults: + run: + working-directory: Frontend + +permissions: + checks: write + contents: read + pull-requests: write +jobs: + build_and_preview: + if: ${{ github.event.pull_request.head.repo.full_name == github.repository }} + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: '20' + cache: 'npm' + cache-dependency-path: 'Frontend/package-lock.json' + + # Install dependencies + - name: Install dependencies + run: npm ci + + # Build with environment variables + - name: Build + run: npm run build + env: + CI: false # Prevents treating warnings as errors + REACT_APP_BASE_API_URL: ${{ secrets.REACT_APP_BASE_API_URL }} + + # Deploy preview + - uses: FirebaseExtended/action-hosting-deploy@v0 + with: + repoToken: ${{ secrets.GITHUB_TOKEN }} + firebaseServiceAccount: ${{ secrets.FIREBASE_SERVICE_ACCOUNT_GOHUB_92B6B }} + projectId: gohub-92b6b + entryPoint: 'Frontend' diff --git a/Backend/.gitignore b/Backend/.gitignore index 11d02f1..0f5e833 100644 --- a/Backend/.gitignore +++ b/Backend/.gitignore @@ -19,6 +19,7 @@ __pycache__/ # ignore .env .env +.env.cloud ``` # Ignore IDE-specific files diff --git a/Backend/README.md b/Backend/README.md index e15712d..1c476dd 100644 --- a/Backend/README.md +++ b/Backend/README.md @@ -7,6 +7,11 @@ python verify_db.py # Local migration with model changes ``` cd Backend + +Ensure DATABASE_URL points to SQLite +```bash +export $(cat .env | xargs) # Contains DATABASE_URL=sqlite:///db.sqlite +``` python manage_db.py # Commit the migration files diff --git a/Backend/app/__init__.py b/Backend/app/__init__.py index b9c06d8..434a2ac 100644 --- a/Backend/app/__init__.py +++ b/Backend/app/__init__.py @@ -7,6 +7,7 @@ from flask_jwt_extended import JWTManager from datetime import timedelta import os +import importlib from google.oauth2 import service_account from app.config import AppConfig @@ -41,6 +42,13 @@ def create_app(config_class=AppConfig): "supports_credentials": True # Add this line }}) + # Load config from environment variable or default to AppConfig + config_class = os.getenv('CONFIG_CLASS', 'app.config.AppConfig') + if isinstance(config_class, str): + module_name, class_name = config_class.rsplit('.', 1) + module = importlib.import_module(module_name) + config_class = getattr(module, class_name) + app.config.from_object(config_class) Session(app) diff --git a/Backend/app/config.py b/Backend/app/config.py index 79a9833..27d1262 100644 --- a/Backend/app/config.py +++ b/Backend/app/config.py @@ -1,14 +1,12 @@ -# app/config.py +# Backend/app/config.py import os import secrets from dotenv import load_dotenv from datetime import timedelta -import os load_dotenv() - class AppConfig: SECRET_KEY = secrets.token_hex(24) # Database configuration @@ -16,16 +14,21 @@ class AppConfig: if database_url and database_url.startswith("postgres://"): database_url = database_url.replace("postgres://", "postgresql://") - # Check if running locally or in Cloud Run - if os.getenv('K_SERVICE'): # Running in Cloud Run - # Use secret for password in production - db_password = os.getenv('DB_PASSWORD') - if db_password: - SQLALCHEMY_DATABASE_URI = f"postgresql://postgres:{db_password}@/ngo_connect?host=/cloudsql/gohub-92b6b:us-west1:ngo-connect-db" - else: - SQLALCHEMY_DATABASE_URI = database_url - else: # Running locally - SQLALCHEMY_DATABASE_URI = "sqlite:///db.sqlite" + # Determine environment and database connection + IS_MIGRATION = os.getenv('IS_MIGRATION') == 'true' + IS_CLOUD_RUN = os.getenv('K_SERVICE') is not None + USE_CLOUD_SQL_PROXY = os.getenv('USE_CLOUD_SQL_PROXY') == 'true' + + # Database URI Configuration + if IS_MIGRATION or USE_CLOUD_SQL_PROXY: + # Use Cloud SQL Proxy connection for migrations or when explicitly requested + SQLALCHEMY_DATABASE_URI = f"postgresql://postgres:{os.getenv('DB_PASSWORD')}@127.0.0.1:5432/ngo_connect" + elif IS_CLOUD_RUN: + # Direct Cloud SQL connection for production + SQLALCHEMY_DATABASE_URI = f"postgresql://postgres:{os.getenv('DB_PASSWORD')}@/ngo_connect?host=/cloudsql/gohub-92b6b:us-west1:ngo-connect-db" + else: + # Local development fallback + SQLALCHEMY_DATABASE_URI = database_url or "sqlite:///db.sqlite" # Determine database type to set appropriate configuration is_sqlite = SQLALCHEMY_DATABASE_URI.startswith('sqlite') diff --git a/Backend/app/main/routes.py b/Backend/app/main/routes.py index 46d0bc7..6d64458 100644 --- a/Backend/app/main/routes.py +++ b/Backend/app/main/routes.py @@ -112,22 +112,44 @@ def match_volunteer_skills(): def get_translate_client(): """ - Create and return a Google Translate client using mounted credentials in Cloud Run - or local credentials for development. + Create and return a Google Translate client with proper environment detection: + 1. First check if we're in GitHub Actions + 2. Then check if we're in Cloud Run + 3. Finally fall back to local development """ try: - if os.getenv('K_SERVICE'): # Running in Cloud Run - # Use the mounted secret path directly + # First check if we're in GitHub Actions + if os.getenv('GITHUB_ACTIONS'): + creds_path = os.getenv('GOOGLE_APPLICATION_CREDENTIALS') + if not creds_path or not os.path.exists(creds_path): + logger.warning("Skipping translation client initialization during GitHub Actions") + return None + credentials = service_account.Credentials.from_service_account_file( - '/secrets/google-creds/key.json', + creds_path, scopes=['https://www.googleapis.com/auth/cloud-translation'] ) return translate.Client(credentials=credentials) + + # Then check if we're in Cloud Run + elif os.getenv('K_SERVICE'): + creds_path = '/secrets/google-creds/key.json' + if not os.path.exists(creds_path): + logger.warning(f"Cloud Run credentials not found at {creds_path}") + return None + + credentials = service_account.Credentials.from_service_account_file( + creds_path, + scopes=['https://www.googleapis.com/auth/cloud-translation'] + ) + return translate.Client(credentials=credentials) + + # Finally, local development else: - # Local development return translate.Client() + except Exception as e: - logger.error(f"Error initializing Google Translate client: {str(e)}") + logger.warning(f"Error initializing Google Translate client: {str(e)} - translation features will be disabled") return None # Initialize the client when the module is imported diff --git a/Backend/manage_db.py b/Backend/manage_db.py index 18c06e8..1c7a0ff 100644 --- a/Backend/manage_db.py +++ b/Backend/manage_db.py @@ -3,6 +3,8 @@ import logging from flask_migrate import upgrade from app import create_app, db +from alembic.util.exc import CommandError +from sqlalchemy.sql import text # Set up root logger to ensure all logs are captured logging.basicConfig( @@ -18,6 +20,7 @@ # Ensure alembic logging is visible logging.getLogger('alembic').setLevel(logging.INFO) +logging.getLogger('sqlalchemy').setLevel(logging.INFO) def run_migrations(): try: @@ -25,15 +28,33 @@ def run_migrations(): with app.app_context(): logger.info("Starting database migrations...") - # Add explicit logging before and after upgrade - logger.info("Running upgrade command...") - result = upgrade() - logger.info(f"Upgrade command completed with result: {result}") + # Verify database connection first + try: + with db.engine.connect() as conn: + result = conn.execute(text("SELECT 1")) + logger.info("Database connection verified") + except Exception as e: + logger.error(f"Database connection failed: {str(e)}") + return False + + try: + logger.info("Running upgrade command...") + result = upgrade(sql=True) # Add sql=True to see the SQL being executed + logger.info(f"Migration SQL: {result}") + + result = upgrade() # Actually run the migration + logger.info(f"Upgrade command completed with result: {result}") + except CommandError as e: + logger.error(f"Alembic command error: {str(e)}") + return False + except Exception as e: + logger.error(f"Migration error: {str(e)}", exc_info=True) + return False # Verify database state after migration logger.info("Verifying database state...") with db.engine.connect() as conn: - logger.info("Database connection successful") + logger.info("Post-migration database connection successful") logger.info("Database migrations completed successfully") return True diff --git a/Backend/migrations/versions/dc151b9e439f_fresh_baseline.py b/Backend/migrations/versions/dc151b9e439f_fresh_baseline.py new file mode 100644 index 0000000..f94ffdf --- /dev/null +++ b/Backend/migrations/versions/dc151b9e439f_fresh_baseline.py @@ -0,0 +1,24 @@ +"""fresh_baseline + +Revision ID: dc151b9e439f +Revises: +Create Date: 2025-01-27 14:57:08.774830 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = 'dc151b9e439f' +down_revision = None +branch_labels = None +depends_on = None + + +def upgrade(): + pass + + +def downgrade(): + pass diff --git a/Backend/migrations/versions/6558698f13fe_initial_migration.py b/Backend/migrations/versions/dc8b189a250c_update_cloudsql_to_match_local.py similarity index 63% rename from Backend/migrations/versions/6558698f13fe_initial_migration.py rename to Backend/migrations/versions/dc8b189a250c_update_cloudsql_to_match_local.py index 79d9a9e..a28e253 100644 --- a/Backend/migrations/versions/6558698f13fe_initial_migration.py +++ b/Backend/migrations/versions/dc8b189a250c_update_cloudsql_to_match_local.py @@ -1,8 +1,8 @@ -"""Initial migration +"""update_cloudsql_to_match_local -Revision ID: 6558698f13fe -Revises: -Create Date: 2024-12-27 16:02:17.986405 +Revision ID: dc8b189a250c +Revises: dc151b9e439f +Create Date: 2025-01-27 14:57:47.913774 """ from alembic import op @@ -10,8 +10,8 @@ # revision identifiers, used by Alembic. -revision = '6558698f13fe' -down_revision = None +revision = 'dc8b189a250c' +down_revision = 'dc151b9e439f' branch_labels = None depends_on = None @@ -19,7 +19,6 @@ def upgrade(): # ### commands auto generated by Alembic - please adjust! ### op.drop_table('social_media_link') - op.drop_table('mpesa_config') with op.batch_alter_table('org_profile', schema=None) as batch_op: batch_op.add_column(sa.Column('org_logo_filename', sa.String(length=200), nullable=True)) batch_op.add_column(sa.Column('org_cover_photo_filename', sa.String(length=200), nullable=True)) @@ -30,8 +29,8 @@ def upgrade(): batch_op.add_column(sa.Column('org_instagram', sa.String(length=200), nullable=True)) batch_op.add_column(sa.Column('org_linkedin', sa.String(length=200), nullable=True)) batch_op.add_column(sa.Column('org_youtube', sa.String(length=200), nullable=True)) - batch_op.drop_column('org_logo') batch_op.drop_column('org_cover_photo') + batch_op.drop_column('org_logo') # ### end Alembic commands ### @@ -39,8 +38,8 @@ def upgrade(): def downgrade(): # ### commands auto generated by Alembic - please adjust! ### with op.batch_alter_table('org_profile', schema=None) as batch_op: - batch_op.add_column(sa.Column('org_cover_photo', sa.VARCHAR(length=200), nullable=True)) - batch_op.add_column(sa.Column('org_logo', sa.VARCHAR(length=200), nullable=True)) + batch_op.add_column(sa.Column('org_logo', sa.VARCHAR(length=200), autoincrement=False, nullable=True)) + batch_op.add_column(sa.Column('org_cover_photo', sa.VARCHAR(length=200), autoincrement=False, nullable=True)) batch_op.drop_column('org_youtube') batch_op.drop_column('org_linkedin') batch_op.drop_column('org_instagram') @@ -51,24 +50,12 @@ def downgrade(): batch_op.drop_column('org_cover_photo_filename') batch_op.drop_column('org_logo_filename') - op.create_table('mpesa_config', - sa.Column('id', sa.INTEGER(), nullable=False), - sa.Column('org_profile_id', sa.INTEGER(), nullable=False), - sa.Column('merchant_name', sa.VARCHAR(length=255), nullable=False), - sa.Column('payment_type', sa.VARCHAR(length=10), nullable=False), - sa.Column('identifier', sa.VARCHAR(length=50), nullable=False), - sa.Column('created_at', sa.DATETIME(), nullable=True), - sa.Column('updated_at', sa.DATETIME(), nullable=True), - sa.ForeignKeyConstraint(['org_profile_id'], ['org_profile.id'], ), - sa.PrimaryKeyConstraint('id'), - sa.UniqueConstraint('org_profile_id') - ) op.create_table('social_media_link', - sa.Column('id', sa.INTEGER(), nullable=False), - sa.Column('org_id', sa.INTEGER(), nullable=False), - sa.Column('platform', sa.VARCHAR(length=200), nullable=False), - sa.Column('url', sa.VARCHAR(length=500), nullable=False), - sa.ForeignKeyConstraint(['org_id'], ['org_profile.id'], ), - sa.PrimaryKeyConstraint('id') + sa.Column('id', sa.INTEGER(), autoincrement=True, nullable=False), + sa.Column('org_id', sa.INTEGER(), autoincrement=False, nullable=False), + sa.Column('platform', sa.VARCHAR(length=200), autoincrement=False, nullable=False), + sa.Column('url', sa.VARCHAR(length=500), autoincrement=False, nullable=False), + sa.ForeignKeyConstraint(['org_id'], ['org_profile.id'], name='social_media_link_org_id_fkey'), + sa.PrimaryKeyConstraint('id', name='social_media_link_pkey') ) # ### end Alembic commands ### diff --git a/DEPLOYMENT.md b/DEPLOYMENT.md index 032a9a7..6ef9e8f 100644 --- a/DEPLOYMENT.md +++ b/DEPLOYMENT.md @@ -40,6 +40,37 @@ This application is deployed using Google Cloud Platform and Firebase, with auto - Required for local development - Enables local testing with production database +#### Migrations +Step-by-step process for updating the Cloud SQL database to match DB models: + +1. Ensure Cloud SQL Proxy is running: +```bash +./cloud-sql-proxy gohub-92b6b:us-west1:ngo-connect-db +``` + +2. Set database URL: +```bash +export $(cat .env.cloud | xargs) +export DATABASE_URL="postgresql://postgres:your_password@127.0.0.1:5432/ngo_connect" +``` + +3. Generate a migration for the changes: +```bash +flask db revision --autogenerate -m "describe_changes" +``` + +4. Review the generated migration file in migrations/versions/ to make sure it captures the intended changes + +5. Apply the migration to Cloud SQL: +```bash +flask db upgrade +``` + +Optional commands that are helpful: +- `flask db current` - shows current revision +- `flask db history` - shows migration history +- `flask db show` - shows current database structure + ### 2. Backend Setup (Cloud Run) #### Environment Variables @@ -188,3 +219,5 @@ firebase hosting:sites:list # View detailed hosting information firebase hosting:site:get ``` + + diff --git a/Frontend/.github/workflows/firebase-hosting-merge.yml b/Frontend/.github/workflows/firebase-hosting-merge.yml deleted file mode 100644 index 6fb10a3..0000000 --- a/Frontend/.github/workflows/firebase-hosting-merge.yml +++ /dev/null @@ -1,20 +0,0 @@ -# This file was auto-generated by the Firebase CLI -# https://github.com/firebase/firebase-tools - -name: Deploy to Firebase Hosting on merge -on: - push: - branches: - - main -jobs: - build_and_deploy: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - run: npm ci && npm run build - - uses: FirebaseExtended/action-hosting-deploy@v0 - with: - repoToken: ${{ secrets.GITHUB_TOKEN }} - firebaseServiceAccount: ${{ secrets.FIREBASE_SERVICE_ACCOUNT_GOHUB_92B6B }} - channelId: live - projectId: gohub-92b6b diff --git a/Frontend/.github/workflows/firebase-hosting-pull-request.yml b/Frontend/.github/workflows/firebase-hosting-pull-request.yml deleted file mode 100644 index a116d98..0000000 --- a/Frontend/.github/workflows/firebase-hosting-pull-request.yml +++ /dev/null @@ -1,21 +0,0 @@ -# This file was auto-generated by the Firebase CLI -# https://github.com/firebase/firebase-tools - -name: Deploy to Firebase Hosting on PR -on: pull_request -permissions: - checks: write - contents: read - pull-requests: write -jobs: - build_and_preview: - if: ${{ github.event.pull_request.head.repo.full_name == github.repository }} - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - run: npm ci && npm run build - - uses: FirebaseExtended/action-hosting-deploy@v0 - with: - repoToken: ${{ secrets.GITHUB_TOKEN }} - firebaseServiceAccount: ${{ secrets.FIREBASE_SERVICE_ACCOUNT_GOHUB_92B6B }} - projectId: gohub-92b6b diff --git a/Frontend/src/components/OrgProfileFormComponents/ProgramsInitiatives.js b/Frontend/src/components/OrgProfileFormComponents/ProgramsInitiatives.js index 14cad5d..5ed7c9d 100644 --- a/Frontend/src/components/OrgProfileFormComponents/ProgramsInitiatives.js +++ b/Frontend/src/components/OrgProfileFormComponents/ProgramsInitiatives.js @@ -64,7 +64,7 @@ const ProgramInitiativesList = forwardRef(({ }, ref) => { // Initialize with provided initiatives or default empty one const [initiatives, setInitiatives] = useState(() => { - if (initialInitiatives.length > 0) { + if (initialInitiatives && initialInitiatives.length > 0) { // Transform incoming data to match component format return initialInitiatives.map(initiative => ({ initiativeName: initiative.initiative_name, diff --git a/Frontend/src/components/OrgProfileFormComponents/SupportNeeds.js b/Frontend/src/components/OrgProfileFormComponents/SupportNeeds.js index c769eee..5a97118 100644 --- a/Frontend/src/components/OrgProfileFormComponents/SupportNeeds.js +++ b/Frontend/src/components/OrgProfileFormComponents/SupportNeeds.js @@ -90,17 +90,21 @@ const SupportNeeds = forwardRef(({ initialTechSkills = [], initialNonTechSkills // Combine and deduplicate skills const techList = Array.from(new Set([ - ...skills.filter(s => s.status === 'tech').map(s => s.value), - ...techSkillOptions.map(s => s.value) - ])).map(value => ({ + ...(skills?.filter(s => s?.status === 'tech')?.map(s => s?.value) || []), + ...(techSkillOptions?.map(s => s?.value) || []) + ])) + .filter(value => value) // Remove any undefined/null values + .map(value => ({ value, label: value.charAt(0).toUpperCase() + value.slice(1) })); const nonTechList = Array.from(new Set([ - ...skills.filter(s => s.status === 'non-tech').map(s => s.value), - ...nonTechSkillOptions.map(s => s.value) - ])).map(value => ({ + ...(skills?.filter(s => s?.status === 'non-tech')?.map(s => s?.value) || []), + ...(nonTechSkillOptions?.map(s => s?.value) || []) + ])) + .filter(value => value) // Remove any undefined/null values + .map(value => ({ value, label: value.charAt(0).toUpperCase() + value.slice(1) }));