diff --git a/docker-compose.yml b/docker-compose.yml index 6db8f404f5..06f8edd7ff 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -122,9 +122,6 @@ services: - /app/node_modules/ environment: - VITE_API_URL=http://api.${FMTM_DOMAIN}:${FMTM_DEV_PORT:-7050} - - VITE_ODK_CENTRAL_URL=${ODK_CENTRAL_URL} - - VITE_ODK_CENTRAL_USER=${ODK_CENTRAL_USER} - - VITE_ODK_CENTRAL_PASSWD=${ODK_CENTRAL_PASSWD} ports: - "7051:7051" networks: diff --git a/src/backend/Dockerfile b/src/backend/Dockerfile index 9035aedafe..77f41b724c 100644 --- a/src/backend/Dockerfile +++ b/src/backend/Dockerfile @@ -96,6 +96,7 @@ RUN set -ex \ -y --no-install-recommends \ "nano" \ "curl" \ + "gettext-base" \ "libpcre3" \ "mime-support" \ "postgresql-client" \ diff --git a/src/backend/app/config.py b/src/backend/app/config.py index af84897407..749b518694 100644 --- a/src/backend/app/config.py +++ b/src/backend/app/config.py @@ -176,7 +176,7 @@ def get_cipher_suite(): return Fernet(settings.ENCRYPTION_KEY) -def encrypt_value(password: str) -> str: +def encrypt_value(password: Union[str, HttpUrlStr]) -> str: """Encrypt value before going to the DB.""" cipher_suite = get_cipher_suite() encrypted_password = cipher_suite.encrypt(password.encode("utf-8")) diff --git a/src/backend/app/db/db_models.py b/src/backend/app/db/db_models.py index 10f5ea2e34..f789250c3e 100644 --- a/src/backend/app/db/db_models.py +++ b/src/backend/app/db/db_models.py @@ -93,7 +93,7 @@ class DbUser(Base): __tablename__ = "users" - id = cast(int, Column(BigInteger, primary_key=True, index=True)) + id = cast(int, Column(BigInteger, primary_key=True)) username = cast(str, Column(String, unique=True)) profile_img = cast(str, Column(String)) role = cast(UserRole, Column(Enum(UserRole), default=UserRole.MAPPER)) diff --git a/src/backend/app/main.py b/src/backend/app/main.py index 3940b4f6c6..d206619f43 100644 --- a/src/backend/app/main.py +++ b/src/backend/app/main.py @@ -36,6 +36,7 @@ from app.db.database import get_db from app.models.enums import HTTPStatus from app.organisations import organisation_routes +from app.organisations.organisation_crud import init_admin_org from app.projects import project_routes from app.projects.project_crud import read_xlsforms from app.submissions import submission_routes @@ -54,8 +55,11 @@ async def lifespan(app: FastAPI): """FastAPI startup/shutdown event.""" log.debug("Starting up FastAPI server.") + db_conn = next(get_db()) + log.debug("Initialising admin org and user in DB.") + await init_admin_org(db_conn) log.debug("Reading XLSForms from DB.") - await read_xlsforms(next(get_db()), xlsforms_path) + await read_xlsforms(db_conn, xlsforms_path) yield diff --git a/src/backend/app/organisations/organisation_crud.py b/src/backend/app/organisations/organisation_crud.py index b5de5ec413..3536b97457 100644 --- a/src/backend/app/organisations/organisation_crud.py +++ b/src/backend/app/organisations/organisation_crud.py @@ -22,11 +22,11 @@ from fastapi import File, HTTPException, Response, UploadFile from loguru import logger as log -from sqlalchemy import update +from sqlalchemy import text, update from sqlalchemy.orm import Session from app.auth.osm import AuthUser -from app.config import settings +from app.config import encrypt_value, settings from app.db import db_models from app.models.enums import HTTPStatus, UserRole from app.organisations.organisation_deps import ( @@ -37,6 +37,93 @@ from app.users.user_crud import get_user +async def init_admin_org(db: Session): + """Init admin org and user at application startup.""" + sql = text( + """ + -- Start a transaction + BEGIN; + + -- Insert FMTM Public Beta organisation + INSERT INTO public.organisations ( + name, + slug, + logo, + description, + url, + type, + approved, + odk_central_url, + odk_central_user, + odk_central_password + ) + VALUES ( + 'FMTM Public Beta', + 'fmtm-public-beta', + 'https://avatars.githubusercontent.com/u/458752?s=280&v=4', + 'HOTOSM Public Beta for FMTM.', + 'https://hotosm.org', + 'FREE', + true, + :odk_url, + :odk_user, + :odk_pass + ) + ON CONFLICT ("name") DO NOTHING; + + -- Insert svcfmtm admin user + INSERT INTO public.users ( + id, + username, + role, + name, + email_address, + is_email_verified, + mapping_level, + tasks_mapped, + tasks_validated, + tasks_invalidated + ) + VALUES ( + :user_id, + :username, + 'ADMIN', + 'Admin', + :odk_user, + true, + 'ADVANCED', + 0, + 0, + 0 + ) + ON CONFLICT ("username") DO NOTHING; + + -- Set svcfmtm user as org admin + WITH org_cte AS ( + SELECT id FROM public.organisations + WHERE name = 'FMTM Public Beta' + ) + INSERT INTO public.organisation_managers (organisation_id, user_id) + SELECT (SELECT id FROM org_cte), :user_id + ON CONFLICT DO NOTHING; + + -- Commit the transaction + COMMIT; + """ + ) + + db.execute( + sql, + { + "user_id": 20386219, + "username": "svcfmtm", + "odk_url": settings.ODK_CENTRAL_URL, + "odk_user": settings.ODK_CENTRAL_USER, + "odk_pass": encrypt_value(settings.ODK_CENTRAL_PASSWD), + }, + ) + + async def get_organisations( db: Session, current_user: AuthUser, diff --git a/src/backend/migrate-entrypoint.sh b/src/backend/migrate-entrypoint.sh index f7cee00b89..24c4d2f178 100644 --- a/src/backend/migrate-entrypoint.sh +++ b/src/backend/migrate-entrypoint.sh @@ -128,7 +128,7 @@ backup_db() { --host "$FMTM_DB_HOST" --username "$FMTM_DB_USER" "$FMTM_DB_NAME" echo "gzipping file --> ${db_backup_file}.gz" - gzip "$db_backup_file" + gzip --force "$db_backup_file" db_backup_file="${db_backup_file}.gz" BUCKET_NAME="fmtm-db-backups" @@ -145,10 +145,11 @@ execute_migrations() { for script_name in "${scripts_to_execute[@]}"; do script_file="/opt/migrations/$script_name" pretty_echo "Executing migration: $script_name" - psql "$db_url" -a -f "$script_file" - - # Add an entry in the migrations table to indicate completion - psql "$db_url" <