Merge branch 'dev' into Inconsistent-Shift-Booking-Status
This commit is contained in:
192
makefiles/backend.mk
Normal file
192
makefiles/backend.mk
Normal file
@@ -0,0 +1,192 @@
|
||||
# --- Backend Foundation (Cloud Run + Workers) ---
|
||||
|
||||
BACKEND_REGION ?= us-central1
|
||||
BACKEND_ARTIFACT_REPO ?= krow-backend
|
||||
|
||||
BACKEND_CORE_SERVICE_NAME ?= krow-core-api
|
||||
BACKEND_COMMAND_SERVICE_NAME ?= krow-command-api
|
||||
BACKEND_RUNTIME_SA_NAME ?= krow-backend-runtime
|
||||
BACKEND_RUNTIME_SA_EMAIL := $(BACKEND_RUNTIME_SA_NAME)@$(GCP_PROJECT_ID).iam.gserviceaccount.com
|
||||
|
||||
BACKEND_CORE_DIR ?= backend/core-api
|
||||
BACKEND_COMMAND_DIR ?= backend/command-api
|
||||
BACKEND_WORKERS_DIR ?= backend/cloud-functions
|
||||
|
||||
BACKEND_DEV_PUBLIC_BUCKET ?= krow-workforce-dev-public
|
||||
BACKEND_DEV_PRIVATE_BUCKET ?= krow-workforce-dev-private
|
||||
BACKEND_STAGING_PUBLIC_BUCKET ?= krow-workforce-staging-public
|
||||
BACKEND_STAGING_PRIVATE_BUCKET ?= krow-workforce-staging-private
|
||||
|
||||
ifeq ($(ENV),staging)
|
||||
BACKEND_PUBLIC_BUCKET := $(BACKEND_STAGING_PUBLIC_BUCKET)
|
||||
BACKEND_PRIVATE_BUCKET := $(BACKEND_STAGING_PRIVATE_BUCKET)
|
||||
BACKEND_RUN_AUTH_FLAG := --no-allow-unauthenticated
|
||||
else
|
||||
BACKEND_PUBLIC_BUCKET := $(BACKEND_DEV_PUBLIC_BUCKET)
|
||||
BACKEND_PRIVATE_BUCKET := $(BACKEND_DEV_PRIVATE_BUCKET)
|
||||
BACKEND_RUN_AUTH_FLAG := --allow-unauthenticated
|
||||
endif
|
||||
|
||||
BACKEND_CORE_IMAGE ?= $(BACKEND_REGION)-docker.pkg.dev/$(GCP_PROJECT_ID)/$(BACKEND_ARTIFACT_REPO)/core-api:latest
|
||||
BACKEND_COMMAND_IMAGE ?= $(BACKEND_REGION)-docker.pkg.dev/$(GCP_PROJECT_ID)/$(BACKEND_ARTIFACT_REPO)/command-api:latest
|
||||
BACKEND_LOG_LIMIT ?= 100
|
||||
BACKEND_LLM_MODEL ?= gemini-2.0-flash-001
|
||||
BACKEND_VERIFICATION_ATTIRE_MODEL ?= gemini-2.0-flash-lite-001
|
||||
BACKEND_VERIFICATION_PROVIDER_TIMEOUT_MS ?= 8000
|
||||
BACKEND_MAX_SIGNED_URL_SECONDS ?= 900
|
||||
BACKEND_LLM_RATE_LIMIT_PER_MINUTE ?= 20
|
||||
|
||||
.PHONY: backend-help backend-enable-apis backend-bootstrap-dev backend-migrate-idempotency backend-deploy-core backend-deploy-commands backend-deploy-workers backend-smoke-core backend-smoke-commands backend-logs-core
|
||||
|
||||
backend-help:
|
||||
@echo "--> Backend Foundation Commands"
|
||||
@echo " make backend-enable-apis [ENV=dev] Enable Cloud Run/Functions/Build/Secret APIs"
|
||||
@echo " make backend-bootstrap-dev Bootstrap artifact repo, runtime SA, and buckets"
|
||||
@echo " make backend-migrate-idempotency Create/upgrade idempotency table in Cloud SQL"
|
||||
@echo " make backend-deploy-core [ENV=dev] Build + deploy core API service"
|
||||
@echo " make backend-deploy-commands [ENV=dev] Build + deploy command API service"
|
||||
@echo " make backend-deploy-workers [ENV=dev] Deploy worker scaffold"
|
||||
@echo " make backend-smoke-core [ENV=dev] Smoke test core /health"
|
||||
@echo " make backend-smoke-commands [ENV=dev] Smoke test commands /health"
|
||||
@echo " make backend-logs-core [ENV=dev] Read core service logs"
|
||||
|
||||
backend-enable-apis:
|
||||
@echo "--> Enabling backend APIs on project [$(GCP_PROJECT_ID)]..."
|
||||
@for api in \
|
||||
run.googleapis.com \
|
||||
cloudbuild.googleapis.com \
|
||||
artifactregistry.googleapis.com \
|
||||
secretmanager.googleapis.com \
|
||||
cloudfunctions.googleapis.com \
|
||||
eventarc.googleapis.com \
|
||||
aiplatform.googleapis.com \
|
||||
storage.googleapis.com \
|
||||
iam.googleapis.com \
|
||||
iamcredentials.googleapis.com \
|
||||
serviceusage.googleapis.com \
|
||||
firebase.googleapis.com; do \
|
||||
echo " - $$api"; \
|
||||
gcloud services enable $$api --project=$(GCP_PROJECT_ID); \
|
||||
done
|
||||
@echo "✅ Backend APIs enabled."
|
||||
|
||||
backend-bootstrap-dev: backend-enable-apis
|
||||
@echo "--> Bootstrapping backend foundation for [$(ENV)] on project [$(GCP_PROJECT_ID)]..."
|
||||
@echo "--> Ensuring Artifact Registry repo [$(BACKEND_ARTIFACT_REPO)] exists..."
|
||||
@if ! gcloud artifacts repositories describe $(BACKEND_ARTIFACT_REPO) --location=$(BACKEND_REGION) --project=$(GCP_PROJECT_ID) >/dev/null 2>&1; then \
|
||||
gcloud artifacts repositories create $(BACKEND_ARTIFACT_REPO) \
|
||||
--repository-format=docker \
|
||||
--location=$(BACKEND_REGION) \
|
||||
--description="KROW backend services" \
|
||||
--project=$(GCP_PROJECT_ID); \
|
||||
else \
|
||||
echo " - Artifact Registry repo already exists."; \
|
||||
fi
|
||||
@echo "--> Ensuring runtime service account [$(BACKEND_RUNTIME_SA_NAME)] exists..."
|
||||
@if ! gcloud iam service-accounts describe $(BACKEND_RUNTIME_SA_EMAIL) --project=$(GCP_PROJECT_ID) >/dev/null 2>&1; then \
|
||||
gcloud iam service-accounts create $(BACKEND_RUNTIME_SA_NAME) \
|
||||
--display-name="KROW Backend Runtime" \
|
||||
--project=$(GCP_PROJECT_ID); \
|
||||
else \
|
||||
echo " - Runtime service account already exists."; \
|
||||
fi
|
||||
@echo "--> Ensuring runtime service account IAM roles..."
|
||||
@gcloud projects add-iam-policy-binding $(GCP_PROJECT_ID) \
|
||||
--member="serviceAccount:$(BACKEND_RUNTIME_SA_EMAIL)" \
|
||||
--role="roles/storage.objectAdmin" \
|
||||
--quiet >/dev/null
|
||||
@gcloud projects add-iam-policy-binding $(GCP_PROJECT_ID) \
|
||||
--member="serviceAccount:$(BACKEND_RUNTIME_SA_EMAIL)" \
|
||||
--role="roles/aiplatform.user" \
|
||||
--quiet >/dev/null
|
||||
@gcloud iam service-accounts add-iam-policy-binding $(BACKEND_RUNTIME_SA_EMAIL) \
|
||||
--member="serviceAccount:$(BACKEND_RUNTIME_SA_EMAIL)" \
|
||||
--role="roles/iam.serviceAccountTokenCreator" \
|
||||
--project=$(GCP_PROJECT_ID) \
|
||||
--quiet >/dev/null
|
||||
@echo "--> Ensuring storage buckets exist..."
|
||||
@if ! gcloud storage buckets describe gs://$(BACKEND_PUBLIC_BUCKET) --project=$(GCP_PROJECT_ID) >/dev/null 2>&1; then \
|
||||
gcloud storage buckets create gs://$(BACKEND_PUBLIC_BUCKET) --location=$(BACKEND_REGION) --project=$(GCP_PROJECT_ID); \
|
||||
else \
|
||||
echo " - Public bucket already exists: $(BACKEND_PUBLIC_BUCKET)"; \
|
||||
fi
|
||||
@if ! gcloud storage buckets describe gs://$(BACKEND_PRIVATE_BUCKET) --project=$(GCP_PROJECT_ID) >/dev/null 2>&1; then \
|
||||
gcloud storage buckets create gs://$(BACKEND_PRIVATE_BUCKET) --location=$(BACKEND_REGION) --project=$(GCP_PROJECT_ID); \
|
||||
else \
|
||||
echo " - Private bucket already exists: $(BACKEND_PRIVATE_BUCKET)"; \
|
||||
fi
|
||||
@echo "✅ Backend foundation bootstrap complete for [$(ENV)]."
|
||||
|
||||
backend-migrate-idempotency:
|
||||
@echo "--> Applying idempotency table migration..."
|
||||
@test -n "$(IDEMPOTENCY_DATABASE_URL)" || (echo "❌ IDEMPOTENCY_DATABASE_URL is required" && exit 1)
|
||||
@cd $(BACKEND_COMMAND_DIR) && IDEMPOTENCY_DATABASE_URL="$(IDEMPOTENCY_DATABASE_URL)" npm run migrate:idempotency
|
||||
@echo "✅ Idempotency migration applied."
|
||||
|
||||
backend-deploy-core:
|
||||
@echo "--> Deploying core backend service [$(BACKEND_CORE_SERVICE_NAME)] to [$(ENV)]..."
|
||||
@test -d $(BACKEND_CORE_DIR) || (echo "❌ Missing directory: $(BACKEND_CORE_DIR)" && exit 1)
|
||||
@test -f $(BACKEND_CORE_DIR)/Dockerfile || (echo "❌ Missing Dockerfile: $(BACKEND_CORE_DIR)/Dockerfile" && exit 1)
|
||||
@gcloud builds submit $(BACKEND_CORE_DIR) --tag $(BACKEND_CORE_IMAGE) --project=$(GCP_PROJECT_ID)
|
||||
@gcloud run deploy $(BACKEND_CORE_SERVICE_NAME) \
|
||||
--image=$(BACKEND_CORE_IMAGE) \
|
||||
--region=$(BACKEND_REGION) \
|
||||
--project=$(GCP_PROJECT_ID) \
|
||||
--service-account=$(BACKEND_RUNTIME_SA_EMAIL) \
|
||||
--set-env-vars=APP_ENV=$(ENV),GCP_PROJECT_ID=$(GCP_PROJECT_ID),PUBLIC_BUCKET=$(BACKEND_PUBLIC_BUCKET),PRIVATE_BUCKET=$(BACKEND_PRIVATE_BUCKET),UPLOAD_MOCK=false,SIGNED_URL_MOCK=false,LLM_MOCK=false,LLM_LOCATION=$(BACKEND_REGION),LLM_MODEL=$(BACKEND_LLM_MODEL),LLM_TIMEOUT_MS=20000,MAX_SIGNED_URL_SECONDS=$(BACKEND_MAX_SIGNED_URL_SECONDS),LLM_RATE_LIMIT_PER_MINUTE=$(BACKEND_LLM_RATE_LIMIT_PER_MINUTE),VERIFICATION_ACCESS_MODE=authenticated,VERIFICATION_REQUIRE_FILE_EXISTS=true,VERIFICATION_ATTIRE_PROVIDER=vertex,VERIFICATION_ATTIRE_MODEL=$(BACKEND_VERIFICATION_ATTIRE_MODEL),VERIFICATION_PROVIDER_TIMEOUT_MS=$(BACKEND_VERIFICATION_PROVIDER_TIMEOUT_MS) \
|
||||
$(BACKEND_RUN_AUTH_FLAG)
|
||||
@echo "✅ Core backend service deployed."
|
||||
|
||||
backend-deploy-commands:
|
||||
@echo "--> Deploying command backend service [$(BACKEND_COMMAND_SERVICE_NAME)] to [$(ENV)]..."
|
||||
@test -d $(BACKEND_COMMAND_DIR) || (echo "❌ Missing directory: $(BACKEND_COMMAND_DIR)" && exit 1)
|
||||
@test -f $(BACKEND_COMMAND_DIR)/Dockerfile || (echo "❌ Missing Dockerfile: $(BACKEND_COMMAND_DIR)/Dockerfile" && exit 1)
|
||||
@gcloud builds submit $(BACKEND_COMMAND_DIR) --tag $(BACKEND_COMMAND_IMAGE) --project=$(GCP_PROJECT_ID)
|
||||
@gcloud run deploy $(BACKEND_COMMAND_SERVICE_NAME) \
|
||||
--image=$(BACKEND_COMMAND_IMAGE) \
|
||||
--region=$(BACKEND_REGION) \
|
||||
--project=$(GCP_PROJECT_ID) \
|
||||
--service-account=$(BACKEND_RUNTIME_SA_EMAIL) \
|
||||
--set-env-vars=APP_ENV=$(ENV),GCP_PROJECT_ID=$(GCP_PROJECT_ID),PUBLIC_BUCKET=$(BACKEND_PUBLIC_BUCKET),PRIVATE_BUCKET=$(BACKEND_PRIVATE_BUCKET) \
|
||||
$(BACKEND_RUN_AUTH_FLAG)
|
||||
@echo "✅ Command backend service deployed."
|
||||
|
||||
backend-deploy-workers:
|
||||
@echo "--> Deploying worker scaffold for [$(ENV)]..."
|
||||
@if [ ! -d "$(BACKEND_WORKERS_DIR)" ]; then \
|
||||
echo "❌ Missing directory: $(BACKEND_WORKERS_DIR)"; \
|
||||
exit 1; \
|
||||
fi
|
||||
@if [ -z "$$(find $(BACKEND_WORKERS_DIR) -mindepth 1 ! -name '.keep' -print -quit)" ]; then \
|
||||
echo "⚠️ No worker code found in $(BACKEND_WORKERS_DIR). Skipping deployment."; \
|
||||
exit 0; \
|
||||
fi
|
||||
@echo "⚠️ Worker deployment is scaffold-only for now."
|
||||
@echo " Add concrete worker deployment commands once worker code is introduced."
|
||||
|
||||
backend-smoke-core:
|
||||
@echo "--> Running core smoke check..."
|
||||
@URL=$$(gcloud run services describe $(BACKEND_CORE_SERVICE_NAME) --region=$(BACKEND_REGION) --project=$(GCP_PROJECT_ID) --format='value(status.url)'); \
|
||||
if [ -z "$$URL" ]; then \
|
||||
echo "❌ Could not resolve URL for service $(BACKEND_CORE_SERVICE_NAME)"; \
|
||||
exit 1; \
|
||||
fi; \
|
||||
TOKEN=$$(gcloud auth print-identity-token); \
|
||||
curl -fsS -H "Authorization: Bearer $$TOKEN" "$$URL/health" >/dev/null && echo "✅ Core smoke check passed: $$URL/health"
|
||||
|
||||
backend-smoke-commands:
|
||||
@echo "--> Running commands smoke check..."
|
||||
@URL=$$(gcloud run services describe $(BACKEND_COMMAND_SERVICE_NAME) --region=$(BACKEND_REGION) --project=$(GCP_PROJECT_ID) --format='value(status.url)'); \
|
||||
if [ -z "$$URL" ]; then \
|
||||
echo "❌ Could not resolve URL for service $(BACKEND_COMMAND_SERVICE_NAME)"; \
|
||||
exit 1; \
|
||||
fi; \
|
||||
TOKEN=$$(gcloud auth print-identity-token); \
|
||||
curl -fsS -H "Authorization: Bearer $$TOKEN" "$$URL/health" >/dev/null && echo "✅ Commands smoke check passed: $$URL/health"
|
||||
|
||||
backend-logs-core:
|
||||
@echo "--> Reading logs for core backend service [$(BACKEND_CORE_SERVICE_NAME)]..."
|
||||
@gcloud run services logs read $(BACKEND_CORE_SERVICE_NAME) \
|
||||
--region=$(BACKEND_REGION) \
|
||||
--project=$(GCP_PROJECT_ID) \
|
||||
--limit=$(BACKEND_LOG_LIMIT)
|
||||
@@ -1,6 +1,6 @@
|
||||
# --- Mobile App Development ---
|
||||
|
||||
.PHONY: mobile-install mobile-info mobile-analyze mobile-client-dev-android mobile-staff-dev-android mobile-client-build mobile-staff-build mobile-hot-reload mobile-hot-restart
|
||||
.PHONY: mobile-install mobile-info mobile-analyze mobile-client-dev-android mobile-staff-dev-android mobile-client-build mobile-staff-build mobile-hot-reload mobile-hot-restart test-e2e test-e2e-setup test-e2e-client test-e2e-staff
|
||||
|
||||
MOBILE_DIR := apps/mobile
|
||||
|
||||
@@ -69,3 +69,32 @@ mobile-staff-build: dataconnect-generate-sdk
|
||||
melos exec --scope="core_localization" -- "dart run slang" && \
|
||||
melos exec --scope="core_localization" -- "dart run build_runner build --delete-conflicting-outputs" && \
|
||||
melos exec --scope="krowwithus_staff" -- "flutter build $(PLATFORM) --$(MODE) --dart-define-from-file=../../config.dev.json"
|
||||
|
||||
# --- E2E (Maestro) ---
|
||||
# Set env before running: TEST_CLIENT_EMAIL, TEST_CLIENT_PASSWORD, TEST_CLIENT_COMPANY, TEST_STAFF_PHONE, TEST_STAFF_OTP, TEST_STAFF_SIGNUP_PHONE
|
||||
# Example: export TEST_CLIENT_EMAIL=legendary@krowd.com TEST_CLIENT_PASSWORD=Demo2026!
|
||||
# Example: export TEST_STAFF_PHONE=5557654321 TEST_STAFF_OTP=123456
|
||||
test-e2e-setup:
|
||||
@echo "--> Checking Maestro CLI..."
|
||||
@maestro --version
|
||||
@echo "--> Maestro OK. Ensure apps are built & installed (see docs/research/maestro-test-run-instructions.md)"
|
||||
|
||||
test-e2e: test-e2e-setup
|
||||
@echo "--> Running full E2E suite (Client + Staff auth flows)..."
|
||||
@maestro test apps/mobile/apps/client/maestro/auth/sign_in.yaml apps/mobile/apps/client/maestro/auth/sign_up.yaml \
|
||||
apps/mobile/apps/staff/maestro/auth/sign_in.yaml apps/mobile/apps/staff/maestro/auth/sign_up.yaml \
|
||||
-e TEST_CLIENT_EMAIL="$${TEST_CLIENT_EMAIL}" -e TEST_CLIENT_PASSWORD="$${TEST_CLIENT_PASSWORD}" \
|
||||
-e TEST_CLIENT_COMPANY="$${TEST_CLIENT_COMPANY}" -e TEST_STAFF_PHONE="$${TEST_STAFF_PHONE}" \
|
||||
-e TEST_STAFF_OTP="$${TEST_STAFF_OTP}" -e TEST_STAFF_SIGNUP_PHONE="$${TEST_STAFF_SIGNUP_PHONE}"
|
||||
|
||||
test-e2e-client: test-e2e-setup
|
||||
@echo "--> Running Client E2E (sign_in, sign_up)..."
|
||||
@maestro test apps/mobile/apps/client/maestro/auth/sign_in.yaml apps/mobile/apps/client/maestro/auth/sign_up.yaml \
|
||||
-e TEST_CLIENT_EMAIL="$${TEST_CLIENT_EMAIL}" -e TEST_CLIENT_PASSWORD="$${TEST_CLIENT_PASSWORD}" \
|
||||
-e TEST_CLIENT_COMPANY="$${TEST_CLIENT_COMPANY}"
|
||||
|
||||
test-e2e-staff: test-e2e-setup
|
||||
@echo "--> Running Staff E2E (sign_in, sign_up)..."
|
||||
@maestro test apps/mobile/apps/staff/maestro/auth/sign_in.yaml apps/mobile/apps/staff/maestro/auth/sign_up.yaml \
|
||||
-e TEST_STAFF_PHONE="$${TEST_STAFF_PHONE}" -e TEST_STAFF_OTP="$${TEST_STAFF_OTP}" \
|
||||
-e TEST_STAFF_SIGNUP_PHONE="$${TEST_STAFF_SIGNUP_PHONE}"
|
||||
|
||||
Reference in New Issue
Block a user