diff --git a/aws/EKS_DEPLOYMENT.md b/aws/EKS_DEPLOYMENT.md new file mode 100644 index 0000000..f447748 --- /dev/null +++ b/aws/EKS_DEPLOYMENT.md @@ -0,0 +1,225 @@ +# AWS EKS Deployment Guide + +This directory contains the Helm chart and configuration for deploying My Recipes application to Amazon EKS (Elastic Kubernetes Service). + +## Structure + +``` +aws/ +├── my-recipes-chart/ # Base Helm chart with default values +│ ├── Chart.yaml +│ ├── values.yaml # Base configuration (don't modify directly) +│ └── templates/ # Kubernetes resource templates +└── values.yaml # Project-specific values (override base values) +``` + +## Prerequisites + +1. **AWS CLI** - Configured with appropriate credentials +2. **kubectl** - Kubernetes command-line tool +3. **Helm 3** - Package manager for Kubernetes +4. **eksctl** (optional) - For creating EKS clusters + +## Setup Steps + +### 1. Create EKS Cluster (if not already exists) + +```bash +eksctl create cluster \ + --name my-recipes-cluster \ + --region eu-central-1 \ + --nodegroup-name standard-workers \ + --node-type t3.medium \ + --nodes 2 \ + --nodes-min 1 \ + --nodes-max 3 +``` + +### 2. Configure kubectl + +```bash +aws eks update-kubeconfig --region eu-central-1 --name my-recipes-cluster +``` + +### 3. Create Namespace + +```bash +kubectl create namespace my-apps +``` + +### 4. Install Ingress Controller (if not already installed) + +For AWS ALB Ingress Controller: +```bash +# Install AWS Load Balancer Controller +helm repo add eks https://aws.github.io/eks-charts +helm install aws-load-balancer-controller eks/aws-load-balancer-controller \ + -n kube-system \ + --set clusterName=my-recipes-cluster +``` + +Or for NGINX Ingress Controller: +```bash +helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx +helm install nginx-ingress ingress-nginx/ingress-nginx \ + -n ingress-nginx --create-namespace +``` + +### 5. Install cert-manager (for SSL certificates) + +```bash +helm repo add jetstack https://charts.jetstack.io +helm install cert-manager jetstack/cert-manager \ + --namespace cert-manager \ + --create-namespace \ + --set installCRDs=true +``` + +### 6. Configure values.yaml + +Edit `values.yaml` in this directory and update: + +- **Container images**: Update ECR repository URLs +- **Domain names**: Replace `` with your actual domain +- **S3 credentials**: Add your AWS access key and secret key +- **Database**: Configure RDS connection details +- **OAuth**: Update redirect URIs with your domain + +### 7. Create S3 Bucket for Backups + +```bash +aws s3 mb s3://my-recipes-backups --region eu-central-1 +``` + +### 8. Push Docker Images to ECR + +```bash +# Create ECR repositories +aws ecr create-repository --repository-name my-recipes-backend --region eu-central-1 +aws ecr create-repository --repository-name my-recipes-frontend --region eu-central-1 + +# Login to ECR +aws ecr get-login-password --region eu-central-1 | docker login --username AWS --password-stdin .dkr.ecr.eu-central-1.amazonaws.com + +# Build and push backend +cd backend +docker build -t my-recipes-backend . +docker tag my-recipes-backend:latest .dkr.ecr.eu-central-1.amazonaws.com/my-recipes-backend:latest +docker push .dkr.ecr.eu-central-1.amazonaws.com/my-recipes-backend:latest + +# Build and push frontend +cd ../frontend +docker build -t my-recipes-frontend . +docker tag my-recipes-frontend:latest .dkr.ecr.eu-central-1.amazonaws.com/my-recipes-frontend:latest +docker push .dkr.ecr.eu-central-1.amazonaws.com/my-recipes-frontend:latest +``` + +### 9. Deploy with Helm + +```bash +# From the aws directory +helm install my-recipes ./my-recipes-chart \ + -f values.yaml \ + -n my-apps +``` + +### 10. Verify Deployment + +```bash +# Check pods +kubectl get pods -n my-apps + +# Check services +kubectl get svc -n my-apps + +# Check ingress +kubectl get ingress -n my-apps + +# View logs +kubectl logs -f deployment/my-recipes-backend -n my-apps +``` + +## Upgrading + +To update the deployment: + +```bash +# Update values.yaml with new configuration +helm upgrade my-recipes ./my-recipes-chart \ + -f values.yaml \ + -n my-apps +``` + +## Using AWS RDS (Recommended for Production) + +1. Create RDS PostgreSQL instance +2. Configure security groups to allow EKS node group access +3. Update `database` section in `values.yaml` with RDS connection details +4. The chart will automatically use external database instead of in-cluster PostgreSQL + +## Using S3 for Backups + +The application is configured to use AWS S3 for database backups instead of Cloudflare R2. Ensure: + +1. S3 bucket exists and is accessible +2. AWS credentials have appropriate permissions: + - `s3:PutObject` + - `s3:GetObject` + - `s3:ListBucket` + - `s3:DeleteObject` + +## Environment Variables + +The chart automatically creates secrets from `values.yaml`: +- Database credentials +- OAuth client secrets +- Email SMTP credentials +- S3 access keys + +All sensitive data should be stored in AWS Secrets Manager in production and referenced via External Secrets Operator. + +## Monitoring + +To view application logs: + +```bash +# Backend logs +kubectl logs -f deployment/my-recipes-backend -n my-apps + +# Frontend logs +kubectl logs -f deployment/my-recipes-frontend -n my-apps + +# Database logs (if using in-cluster DB) +kubectl logs -f statefulset/my-recipes-db -n my-apps +``` + +## Troubleshooting + +### Pods not starting +```bash +kubectl describe pod -n my-apps +``` + +### Database connection issues +```bash +kubectl exec -it deployment/my-recipes-backend -n my-apps -- env | grep DB_ +``` + +### Ingress not working +```bash +kubectl describe ingress -n my-apps +``` + +## Uninstall + +```bash +helm uninstall my-recipes -n my-apps +``` + +## Cost Optimization + +For non-production environments: +- Reduce replica counts to 1 +- Use smaller instance types (t3.small) +- Use in-cluster PostgreSQL instead of RDS +- Configure cluster autoscaling diff --git a/aws/my-recipes-chart-aws/Chart.yaml b/aws/my-recipes-chart-aws/Chart.yaml new file mode 100644 index 0000000..86c815b --- /dev/null +++ b/aws/my-recipes-chart-aws/Chart.yaml @@ -0,0 +1,14 @@ +apiVersion: v2 +name: my-recipes +description: Complete recipe management application with PostgreSQL, FastAPI backend, and React frontend +type: application +version: 1.0.0 +appVersion: "1.0.0" +keywords: + - recipes + - fastapi + - react + - postgresql +maintainers: + - name: Development Team + diff --git a/aws/my-recipes-chart-aws/templates/add-missing-tables-configmap.yaml b/aws/my-recipes-chart-aws/templates/add-missing-tables-configmap.yaml new file mode 100644 index 0000000..d15cad9 --- /dev/null +++ b/aws/my-recipes-chart-aws/templates/add-missing-tables-configmap.yaml @@ -0,0 +1,45 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ .Release.Name }}-add-missing-tables + namespace: {{ .Values.global.namespace }} +data: + add-tables.sql: | + -- Create grocery lists table + CREATE TABLE IF NOT EXISTS grocery_lists ( + id SERIAL PRIMARY KEY, + name TEXT NOT NULL, + items TEXT[] NOT NULL DEFAULT '{}', + owner_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE, + is_pinned BOOLEAN DEFAULT FALSE, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ); + + -- Create grocery list shares table + CREATE TABLE IF NOT EXISTS grocery_list_shares ( + id SERIAL PRIMARY KEY, + list_id INTEGER NOT NULL REFERENCES grocery_lists(id) ON DELETE CASCADE, + shared_with_user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE, + can_edit BOOLEAN DEFAULT FALSE, + shared_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + UNIQUE(list_id, shared_with_user_id) + ); + + CREATE INDEX IF NOT EXISTS idx_grocery_lists_owner_id ON grocery_lists (owner_id); + CREATE INDEX IF NOT EXISTS idx_grocery_list_shares_list_id ON grocery_list_shares (list_id); + CREATE INDEX IF NOT EXISTS idx_grocery_list_shares_user_id ON grocery_list_shares (shared_with_user_id); + + -- Create notifications table + CREATE TABLE IF NOT EXISTS notifications ( + id SERIAL PRIMARY KEY, + user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE, + type TEXT NOT NULL, + message TEXT NOT NULL, + related_id INTEGER, + is_read BOOLEAN DEFAULT FALSE, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ); + + CREATE INDEX IF NOT EXISTS idx_notifications_user_id ON notifications (user_id); + CREATE INDEX IF NOT EXISTS idx_notifications_is_read ON notifications (is_read); diff --git a/aws/my-recipes-chart-aws/templates/add-missing-tables-job.yaml b/aws/my-recipes-chart-aws/templates/add-missing-tables-job.yaml new file mode 100644 index 0000000..e0ca1bc --- /dev/null +++ b/aws/my-recipes-chart-aws/templates/add-missing-tables-job.yaml @@ -0,0 +1,49 @@ +apiVersion: batch/v1 +kind: Job +metadata: + name: {{ .Release.Name }}-add-missing-tables + namespace: {{ .Values.global.namespace }} + annotations: + "helm.sh/hook": post-upgrade + "helm.sh/hook-weight": "6" + "helm.sh/hook-delete-policy": before-hook-creation +spec: + template: + spec: + restartPolicy: Never + containers: + - name: add-tables + image: postgres:16-alpine + env: + - name: PGHOST + value: {{ .Release.Name }}-db + - name: PGPORT + value: "{{ .Values.postgres.port }}" + - name: PGDATABASE + value: {{ .Values.postgres.database }} + - name: PGUSER + value: {{ .Values.postgres.user }} + - name: PGPASSWORD + valueFrom: + secretKeyRef: + name: {{ .Release.Name }}-db-credentials + key: DB_PASSWORD + command: + - sh + - -c + - | + echo "Waiting for database to be ready..." + until pg_isready -h $PGHOST -p $PGPORT -U $PGUSER; do + echo "Database not ready, waiting..." + sleep 2 + done + echo "Database ready, adding missing tables..." + psql -v ON_ERROR_STOP=1 -f /sql/add-tables.sql + echo "Tables added successfully!" + volumeMounts: + - name: sql + mountPath: /sql + volumes: + - name: sql + configMap: + name: {{ .Release.Name }}-add-missing-tables diff --git a/aws/my-recipes-chart-aws/templates/admin-init-configmap.yaml b/aws/my-recipes-chart-aws/templates/admin-init-configmap.yaml new file mode 100644 index 0000000..7bda3cd --- /dev/null +++ b/aws/my-recipes-chart-aws/templates/admin-init-configmap.yaml @@ -0,0 +1,99 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ .Release.Name }}-admin-init + namespace: {{ .Values.global.namespace }} +data: + create-admin.py: | + #!/usr/bin/env python3 + import os + import sys + import psycopg2 + import bcrypt + from time import sleep + + def wait_for_db(): + """Wait for database to be ready""" + max_retries = 30 + retry_count = 0 + + while retry_count < max_retries: + try: + conn = psycopg2.connect( + host=os.environ['DB_HOST'], + port=os.environ['DB_PORT'], + database=os.environ['DB_NAME'], + user=os.environ['DB_USER'], + password=os.environ['DB_PASSWORD'] + ) + conn.close() + print("✓ Database is ready") + return True + except Exception as e: + retry_count += 1 + print(f"Waiting for database... ({retry_count}/{max_retries})") + sleep(2) + + print("✗ Database connection timeout") + return False + + def create_admin_user(): + """Create admin user if not exists""" + try: + # Hash the password + password = os.environ.get('ADMIN_PASSWORD', 'admin123') + password_hash = bcrypt.hashpw(password.encode('utf-8'), bcrypt.gensalt()).decode('utf-8') + + # Connect to database + conn = psycopg2.connect( + host=os.environ['DB_HOST'], + port=os.environ['DB_PORT'], + database=os.environ['DB_NAME'], + user=os.environ['DB_USER'], + password=os.environ['DB_PASSWORD'] + ) + cur = conn.cursor() + + # Insert admin user + cur.execute(""" + INSERT INTO users (username, email, password_hash, first_name, last_name, display_name, is_admin) + VALUES (%s, %s, %s, %s, %s, %s, %s) + ON CONFLICT (username) DO UPDATE SET + email = EXCLUDED.email, + password_hash = EXCLUDED.password_hash, + first_name = EXCLUDED.first_name, + last_name = EXCLUDED.last_name, + display_name = EXCLUDED.display_name, + is_admin = EXCLUDED.is_admin + """, ( + os.environ.get('ADMIN_USERNAME', 'admin'), + os.environ.get('ADMIN_EMAIL', 'admin@myrecipes.local'), + password_hash, + os.environ.get('ADMIN_FIRST_NAME', 'Admin'), + os.environ.get('ADMIN_LAST_NAME', 'User'), + os.environ.get('ADMIN_DISPLAY_NAME', 'מנהל'), + True + )) + + conn.commit() + cur.close() + conn.close() + + print(f"✓ Admin user '{os.environ.get('ADMIN_USERNAME', 'admin')}' created/updated successfully") + return True + + except Exception as e: + print(f"✗ Error creating admin user: {e}") + return False + + if __name__ == "__main__": + print("Starting admin user initialization...") + + if not wait_for_db(): + sys.exit(1) + + if not create_admin_user(): + sys.exit(1) + + print("✓ Admin user initialization completed") + sys.exit(0) diff --git a/aws/my-recipes-chart-aws/templates/admin-init-job.yaml b/aws/my-recipes-chart-aws/templates/admin-init-job.yaml new file mode 100644 index 0000000..511ec36 --- /dev/null +++ b/aws/my-recipes-chart-aws/templates/admin-init-job.yaml @@ -0,0 +1,75 @@ +apiVersion: batch/v1 +kind: Job +metadata: + name: {{ .Release.Name }}-admin-init-{{ .Release.Revision }} + namespace: {{ .Values.global.namespace }} + labels: + app: {{ .Release.Name }}-admin-init + component: init + annotations: + "helm.sh/hook": post-install,post-upgrade + "helm.sh/hook-weight": "10" + "helm.sh/hook-delete-policy": before-hook-creation +spec: + ttlSecondsAfterFinished: 300 + template: + metadata: + labels: + app: {{ .Release.Name }}-admin-init + spec: + restartPolicy: Never + containers: + - name: admin-init + image: python:3.12-slim + command: + - /bin/sh + - -c + - | + pip install --no-cache-dir psycopg2-binary bcrypt > /dev/null 2>&1 + python3 /scripts/create-admin.py + env: + - name: DB_HOST + valueFrom: + secretKeyRef: + name: {{ .Release.Name }}-db-credentials + key: DB_HOST + - name: DB_PORT + valueFrom: + secretKeyRef: + name: {{ .Release.Name }}-db-credentials + key: DB_PORT + - name: DB_NAME + valueFrom: + secretKeyRef: + name: {{ .Release.Name }}-db-credentials + key: DB_NAME + - name: DB_USER + valueFrom: + secretKeyRef: + name: {{ .Release.Name }}-db-credentials + key: DB_USER + - name: DB_PASSWORD + valueFrom: + secretKeyRef: + name: {{ .Release.Name }}-db-credentials + key: DB_PASSWORD + - name: ADMIN_USERNAME + value: {{ .Values.admin.username | quote }} + - name: ADMIN_EMAIL + value: {{ .Values.admin.email | quote }} + - name: ADMIN_PASSWORD + value: {{ .Values.admin.password | quote }} + - name: ADMIN_FIRST_NAME + value: {{ .Values.admin.firstName | quote }} + - name: ADMIN_LAST_NAME + value: {{ .Values.admin.lastName | quote }} + - name: ADMIN_DISPLAY_NAME + value: {{ .Values.admin.displayName | quote }} + volumeMounts: + - name: init-script + mountPath: /scripts + volumes: + - name: init-script + configMap: + name: {{ .Release.Name }}-admin-init + defaultMode: 0755 diff --git a/aws/my-recipes-chart-aws/templates/app-secrets.yaml b/aws/my-recipes-chart-aws/templates/app-secrets.yaml new file mode 100644 index 0000000..93285f3 --- /dev/null +++ b/aws/my-recipes-chart-aws/templates/app-secrets.yaml @@ -0,0 +1,35 @@ +apiVersion: v1 +kind: Secret +metadata: + name: {{ .Release.Name }}-app-secrets + namespace: {{ .Values.global.namespace }} +type: Opaque +stringData: + # Google OAuth + GOOGLE_CLIENT_ID: {{ .Values.oauth.google.clientId | quote }} + GOOGLE_CLIENT_SECRET: {{ .Values.oauth.google.clientSecret | quote }} + GOOGLE_REDIRECT_URI: {{ .Values.oauth.google.redirectUri | quote }} + + # Microsoft Entra ID (Azure AD) OAuth + AZURE_CLIENT_ID: {{ .Values.oauth.azure.clientId | quote }} + AZURE_CLIENT_SECRET: {{ .Values.oauth.azure.clientSecret | quote }} + AZURE_TENANT_ID: {{ .Values.oauth.azure.tenantId | quote }} + AZURE_REDIRECT_URI: {{ .Values.oauth.azure.redirectUri | quote }} + + # Email Configuration + SMTP_HOST: {{ .Values.email.smtpHost | quote }} + SMTP_PORT: {{ .Values.email.smtpPort | quote }} + SMTP_USER: {{ .Values.email.smtpUser | quote }} + SMTP_PASSWORD: {{ .Values.email.smtpPassword | quote }} + SMTP_FROM: {{ .Values.email.smtpFrom | quote }} + + # Frontend URL for redirects + FRONTEND_URL: {{ .Values.frontend.externalUrl | quote }} + + # S3 Backup Configuration + S3_ENDPOINT: {{ .Values.s3.endpoint | quote }} + S3_ACCESS_KEY: {{ .Values.s3.accessKey | quote }} + S3_SECRET_KEY: {{ .Values.s3.secretKey | quote }} + S3_BUCKET_NAME: {{ .Values.s3.bucketName | quote }} + S3_REGION: {{ .Values.s3.region | quote }} + BACKUP_INTERVAL: {{ .Values.s3.backupInterval | quote }} diff --git a/aws/my-recipes-chart-aws/templates/backend-deployment.yaml b/aws/my-recipes-chart-aws/templates/backend-deployment.yaml new file mode 100644 index 0000000..b7379bd --- /dev/null +++ b/aws/my-recipes-chart-aws/templates/backend-deployment.yaml @@ -0,0 +1,119 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ .Release.Name }}-{{ .Values.backend.name }} + namespace: {{ .Values.global.namespace }} + labels: + app: {{ .Release.Name }}-{{ .Values.backend.name }} + component: backend +spec: + replicas: {{ .Values.backend.replicaCount }} + selector: + matchLabels: + app: {{ .Release.Name }}-{{ .Values.backend.name }} + template: + metadata: + labels: + app: {{ .Release.Name }}-{{ .Values.backend.name }} + component: backend + spec: + initContainers: + - name: db-migration + image: postgres:16-alpine + command: + - /bin/sh + - -c + - | + echo "Waiting for database to be ready..." + until pg_isready -h $DB_HOST -U $DB_USER; do + echo "Database not ready, waiting..." + sleep 2 + done + echo "Database is ready, running migration..." + PGPASSWORD=$DB_PASSWORD psql -h $DB_HOST -U $DB_USER -d $DB_NAME -f /migration/migrate.sql + echo "Migration completed successfully" + env: + - name: DB_HOST + valueFrom: + secretKeyRef: + name: {{ .Release.Name }}-db-credentials + key: DB_HOST + - name: DB_PORT + valueFrom: + secretKeyRef: + name: {{ .Release.Name }}-db-credentials + key: DB_PORT + - name: DB_NAME + valueFrom: + secretKeyRef: + name: {{ .Release.Name }}-db-credentials + key: DB_NAME + - name: DB_USER + valueFrom: + secretKeyRef: + name: {{ .Release.Name }}-db-credentials + key: DB_USER + - name: DB_PASSWORD + valueFrom: + secretKeyRef: + name: {{ .Release.Name }}-db-credentials + key: DB_PASSWORD + volumeMounts: + - name: migration-script + mountPath: /migration + containers: + - name: {{ .Values.backend.name }} + image: "{{ .Values.backend.image.repository }}:{{ .Values.backend.image.tag }}" + imagePullPolicy: {{ .Values.backend.image.pullPolicy }} + ports: + - containerPort: {{ .Values.backend.service.targetPort }} + name: http + protocol: TCP + env: + {{- if .Values.backend.env }} + {{- range $key, $value := .Values.backend.env }} + - name: {{ $key }} + value: {{ $value | quote }} + {{- end }} + {{- end }} + envFrom: + - secretRef: + name: {{ .Release.Name }}-db-credentials + - secretRef: + name: {{ .Release.Name }}-app-secrets + startupProbe: + httpGet: + path: /docs + port: http + initialDelaySeconds: 15 + periodSeconds: 5 + timeoutSeconds: 3 + failureThreshold: 30 + livenessProbe: + httpGet: + path: /docs + port: http + initialDelaySeconds: 30 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 3 + readinessProbe: + httpGet: + path: /docs + port: http + initialDelaySeconds: 10 + periodSeconds: 5 + timeoutSeconds: 3 + failureThreshold: 2 + resources: + requests: + cpu: {{ .Values.backend.resources.requests.cpu }} + memory: {{ .Values.backend.resources.requests.memory }} + limits: + cpu: {{ .Values.backend.resources.limits.cpu }} + memory: {{ .Values.backend.resources.limits.memory }} + volumes: + - name: migration-script + configMap: + name: {{ .Release.Name }}-db-migration + diff --git a/aws/my-recipes-chart-aws/templates/backend-service.yaml b/aws/my-recipes-chart-aws/templates/backend-service.yaml new file mode 100644 index 0000000..6608df6 --- /dev/null +++ b/aws/my-recipes-chart-aws/templates/backend-service.yaml @@ -0,0 +1,17 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ .Release.Name }}-{{ .Values.backend.name }} + namespace: {{ .Values.global.namespace }} + labels: + app: {{ .Release.Name }}-{{ .Values.backend.name }} + component: backend +spec: + type: {{ .Values.backend.service.type }} + ports: + - port: {{ .Values.backend.service.port }} + targetPort: {{ .Values.backend.service.targetPort }} + protocol: TCP + name: http + selector: + app: {{ .Release.Name }}-{{ .Values.backend.name }} diff --git a/aws/my-recipes-chart-aws/templates/db-migration-configmap.yaml b/aws/my-recipes-chart-aws/templates/db-migration-configmap.yaml new file mode 100644 index 0000000..5a7c4da --- /dev/null +++ b/aws/my-recipes-chart-aws/templates/db-migration-configmap.yaml @@ -0,0 +1,54 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ .Release.Name }}-db-migration + namespace: {{ .Values.global.namespace }} +data: + migrate.sql: | + -- Add made_by column to recipes if it doesn't exist + DO $$ + BEGIN + IF NOT EXISTS ( + SELECT 1 FROM information_schema.columns + WHERE table_name = 'recipes' AND column_name = 'made_by' + ) THEN + ALTER TABLE recipes ADD COLUMN made_by TEXT; + END IF; + END $$; + + -- Create index if it doesn't exist + CREATE INDEX IF NOT EXISTS idx_recipes_made_by ON recipes (made_by); + + -- Add is_admin column to users if it doesn't exist + DO $$ + BEGIN + IF NOT EXISTS ( + SELECT 1 FROM information_schema.columns + WHERE table_name = 'users' AND column_name = 'is_admin' + ) THEN + ALTER TABLE users ADD COLUMN is_admin BOOLEAN DEFAULT FALSE; + END IF; + END $$; + + -- Add auth_provider column to users if it doesn't exist + DO $$ + BEGIN + IF NOT EXISTS ( + SELECT 1 FROM information_schema.columns + WHERE table_name = 'users' AND column_name = 'auth_provider' + ) THEN + ALTER TABLE users ADD COLUMN auth_provider TEXT DEFAULT 'local'; + END IF; + END $$; + + -- Verify recipes schema + SELECT column_name, data_type + FROM information_schema.columns + WHERE table_name = 'recipes' + ORDER BY ordinal_position; + + -- Verify users schema + SELECT column_name, data_type + FROM information_schema.columns + WHERE table_name = 'users' + ORDER BY ordinal_position; diff --git a/aws/my-recipes-chart-aws/templates/db-migration-job.yaml b/aws/my-recipes-chart-aws/templates/db-migration-job.yaml new file mode 100644 index 0000000..f153342 --- /dev/null +++ b/aws/my-recipes-chart-aws/templates/db-migration-job.yaml @@ -0,0 +1,69 @@ +apiVersion: batch/v1 +kind: Job +metadata: + name: {{ .Release.Name }}-db-migration-{{ .Release.Revision }} + namespace: {{ .Values.global.namespace }} + labels: + app: {{ .Release.Name }}-db-migration + component: migration + annotations: + "helm.sh/hook": post-upgrade,post-install + "helm.sh/hook-weight": "5" + "helm.sh/hook-delete-policy": before-hook-creation +spec: + ttlSecondsAfterFinished: 300 + template: + metadata: + labels: + app: {{ .Release.Name }}-db-migration + spec: + restartPolicy: Never + containers: + - name: migrate + image: postgres:16-alpine + command: + - /bin/sh + - -c + - | + echo "Waiting for database to be ready..." + until pg_isready -h $DB_HOST -U $DB_USER; do + echo "Database not ready, waiting..." + sleep 2 + done + echo "Database is ready, running migration..." + PGPASSWORD=$DB_PASSWORD psql -h $DB_HOST -U $DB_USER -d $DB_NAME -f /migration/migrate.sql + echo "Migration completed successfully" + env: + - name: DB_HOST + valueFrom: + secretKeyRef: + name: {{ .Release.Name }}-db-credentials + key: DB_HOST + - name: DB_PORT + valueFrom: + secretKeyRef: + name: {{ .Release.Name }}-db-credentials + key: DB_PORT + - name: DB_NAME + valueFrom: + secretKeyRef: + name: {{ .Release.Name }}-db-credentials + key: DB_NAME + - name: DB_USER + valueFrom: + secretKeyRef: + name: {{ .Release.Name }}-db-credentials + key: DB_USER + - name: DB_PASSWORD + valueFrom: + secretKeyRef: + name: {{ .Release.Name }}-db-credentials + key: DB_PASSWORD + volumeMounts: + - name: migration-script + mountPath: /migration + volumes: + - name: migration-script + configMap: + name: {{ .Release.Name }}-db-migration + diff --git a/aws/my-recipes-chart-aws/templates/db-schema-configmap.yaml b/aws/my-recipes-chart-aws/templates/db-schema-configmap.yaml new file mode 100644 index 0000000..c6ee6dc --- /dev/null +++ b/aws/my-recipes-chart-aws/templates/db-schema-configmap.yaml @@ -0,0 +1,134 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ .Release.Name }}-db-schema + namespace: {{ .Values.global.namespace }} +data: + schema.sql: | + -- Create users table + CREATE TABLE IF NOT EXISTS users ( + id SERIAL PRIMARY KEY, + username TEXT UNIQUE NOT NULL, + email TEXT UNIQUE NOT NULL, + password_hash TEXT NOT NULL, + first_name TEXT, + last_name TEXT, + display_name TEXT NOT NULL, + is_admin BOOLEAN DEFAULT FALSE, + auth_provider TEXT DEFAULT 'local', + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ); + + CREATE INDEX IF NOT EXISTS idx_users_username ON users (username); + CREATE INDEX IF NOT EXISTS idx_users_email ON users (email); + + -- Create recipes table (matching backend schema with TEXT[] arrays) + CREATE TABLE IF NOT EXISTS recipes ( + id SERIAL PRIMARY KEY, + name TEXT NOT NULL, + meal_type TEXT NOT NULL, -- breakfast / lunch / dinner / snack + time_minutes INTEGER NOT NULL, + tags TEXT[] NOT NULL DEFAULT '{}', -- {"מהיר", "בריא"} + ingredients TEXT[] NOT NULL DEFAULT '{}', -- {"ביצה", "עגבניה", "מלח"} + steps TEXT[] NOT NULL DEFAULT '{}', -- {"לחתוך", "לבשל", ...} + image TEXT, -- Base64-encoded image or image URL + made_by TEXT, -- Person who created this recipe version + user_id INTEGER REFERENCES users(id) ON DELETE SET NULL, -- Recipe owner + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ); + + -- Indexes for filters + CREATE INDEX IF NOT EXISTS idx_recipes_meal_type + ON recipes (meal_type); + + CREATE INDEX IF NOT EXISTS idx_recipes_time_minutes + ON recipes (time_minutes); + + CREATE INDEX IF NOT EXISTS idx_recipes_made_by + ON recipes (made_by); + + CREATE INDEX IF NOT EXISTS idx_recipes_user_id + ON recipes (user_id); + + -- Add new columns to existing users table + DO $$ + BEGIN + IF NOT EXISTS ( + SELECT 1 FROM information_schema.columns + WHERE table_name = 'users' AND column_name = 'first_name' + ) THEN + ALTER TABLE users ADD COLUMN first_name TEXT; + END IF; + END $$; + + DO $$ + BEGIN + IF NOT EXISTS ( + SELECT 1 FROM information_schema.columns + WHERE table_name = 'users' AND column_name = 'last_name' + ) THEN + ALTER TABLE users ADD COLUMN last_name TEXT; + END IF; + END $$; + + DO $$ + BEGIN + IF NOT EXISTS ( + SELECT 1 FROM information_schema.columns + WHERE table_name = 'users' AND column_name = 'display_name' + ) THEN + ALTER TABLE users ADD COLUMN display_name TEXT; + -- Set display_name to username for existing users + UPDATE users SET display_name = username WHERE display_name IS NULL; + ALTER TABLE users ALTER COLUMN display_name SET NOT NULL; + END IF; + END $$; + + DO $$ + BEGIN + IF NOT EXISTS ( + SELECT 1 FROM information_schema.columns + WHERE table_name = 'users' AND column_name = 'is_admin' + ) THEN + ALTER TABLE users ADD COLUMN is_admin BOOLEAN DEFAULT FALSE; + END IF; + END $$; + + -- Create grocery lists table + CREATE TABLE IF NOT EXISTS grocery_lists ( + id SERIAL PRIMARY KEY, + name TEXT NOT NULL, + items TEXT[] NOT NULL DEFAULT '{}', + owner_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE, + is_pinned BOOLEAN DEFAULT FALSE, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ); + + -- Create grocery list shares table + CREATE TABLE IF NOT EXISTS grocery_list_shares ( + id SERIAL PRIMARY KEY, + list_id INTEGER NOT NULL REFERENCES grocery_lists(id) ON DELETE CASCADE, + shared_with_user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE, + can_edit BOOLEAN DEFAULT FALSE, + shared_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + UNIQUE(list_id, shared_with_user_id) + ); + + CREATE INDEX IF NOT EXISTS idx_grocery_lists_owner_id ON grocery_lists (owner_id); + CREATE INDEX IF NOT EXISTS idx_grocery_list_shares_list_id ON grocery_list_shares (list_id); + CREATE INDEX IF NOT EXISTS idx_grocery_list_shares_user_id ON grocery_list_shares (shared_with_user_id); + + -- Create notifications table + CREATE TABLE IF NOT EXISTS notifications ( + id SERIAL PRIMARY KEY, + user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE, + type TEXT NOT NULL, + message TEXT NOT NULL, + related_id INTEGER, + is_read BOOLEAN DEFAULT FALSE, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ); + + CREATE INDEX IF NOT EXISTS idx_notifications_user_id ON notifications (user_id); + CREATE INDEX IF NOT EXISTS idx_notifications_is_read ON notifications (is_read); diff --git a/aws/my-recipes-chart-aws/templates/db-secret.yaml b/aws/my-recipes-chart-aws/templates/db-secret.yaml new file mode 100644 index 0000000..aef2e21 --- /dev/null +++ b/aws/my-recipes-chart-aws/templates/db-secret.yaml @@ -0,0 +1,23 @@ +apiVersion: v1 +kind: Secret +metadata: + name: {{ .Release.Name }}-db-credentials + namespace: {{ .Values.global.namespace }} +type: Opaque +stringData: + {{- if .Values.database }} + # External database (e.g., AWS RDS) + DB_HOST: {{ .Values.database.host | quote }} + DB_PORT: {{ .Values.database.port | quote }} + DB_NAME: {{ .Values.database.name | quote }} + DB_USER: {{ .Values.database.user | quote }} + DB_PASSWORD: {{ .Values.database.password | quote }} + {{- else }} + # In-cluster PostgreSQL + DB_HOST: {{ printf "%s-%s-headless.%s.svc.cluster.local" .Release.Name .Values.postgres.name .Values.global.namespace }} + DB_PORT: "{{ .Values.postgres.port }}" + DB_NAME: {{ .Values.postgres.database | quote }} + DB_USER: {{ .Values.postgres.user | quote }} + DB_PASSWORD: {{ .Values.postgres.password | quote }} + {{- end }} + diff --git a/aws/my-recipes-chart-aws/templates/db-service.yaml b/aws/my-recipes-chart-aws/templates/db-service.yaml new file mode 100644 index 0000000..c4704d3 --- /dev/null +++ b/aws/my-recipes-chart-aws/templates/db-service.yaml @@ -0,0 +1,39 @@ +{{- if not .Values.database }} +{{- /* Only deploy in-cluster PostgreSQL services if external database is not configured */ -}} +apiVersion: v1 +kind: Service +metadata: + name: {{ .Release.Name }}-{{ .Values.postgres.name }}-headless + namespace: {{ .Values.global.namespace }} + labels: + app: {{ .Release.Name }}-{{ .Values.postgres.name }} + component: database +spec: + clusterIP: None + selector: + app: {{ .Release.Name }}-{{ .Values.postgres.name }} + ports: + - name: postgres + port: {{ .Values.postgres.port }} + targetPort: {{ .Values.postgres.port }} + protocol: TCP +--- +apiVersion: v1 +kind: Service +metadata: + name: {{ .Release.Name }}-{{ .Values.postgres.name }} + namespace: {{ .Values.global.namespace }} + labels: + app: {{ .Release.Name }}-{{ .Values.postgres.name }} + component: database +spec: + type: {{ .Values.postgres.service.type }} + selector: + app: {{ .Release.Name }}-{{ .Values.postgres.name }} + ports: + - name: postgres + port: {{ .Values.postgres.service.port }} + targetPort: {{ .Values.postgres.port }} + protocol: TCP +{{- end }} + diff --git a/aws/my-recipes-chart-aws/templates/db-statefulset.yaml b/aws/my-recipes-chart-aws/templates/db-statefulset.yaml new file mode 100644 index 0000000..93af59a --- /dev/null +++ b/aws/my-recipes-chart-aws/templates/db-statefulset.yaml @@ -0,0 +1,89 @@ +{{- if not .Values.database }} +{{- /* Only deploy in-cluster PostgreSQL if external database is not configured */ -}} +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: {{ .Release.Name }}-{{ .Values.postgres.name }} + namespace: {{ .Values.global.namespace }} + labels: + app: {{ .Release.Name }}-{{ .Values.postgres.name }} + component: database +spec: + serviceName: {{ .Release.Name }}-{{ .Values.postgres.name }}-headless + replicas: 1 + selector: + matchLabels: + app: {{ .Release.Name }}-{{ .Values.postgres.name }} + template: + metadata: + labels: + app: {{ .Release.Name }}-{{ .Values.postgres.name }} + component: database + spec: + containers: + - name: postgres + image: "{{ .Values.postgres.image.repository }}:{{ .Values.postgres.image.tag }}" + imagePullPolicy: {{ .Values.postgres.image.pullPolicy }} + ports: + - containerPort: {{ .Values.postgres.port }} + name: postgres + protocol: TCP + env: + - name: POSTGRES_USER + value: {{ .Values.postgres.user | quote }} + - name: POSTGRES_PASSWORD + value: {{ .Values.postgres.password | quote }} + - name: POSTGRES_DB + value: {{ .Values.postgres.database | quote }} + - name: PGDATA + value: /var/lib/postgresql/data/pgdata + volumeMounts: + - name: data + mountPath: /var/lib/postgresql/data + - name: init-sql + mountPath: /docker-entrypoint-initdb.d + livenessProbe: + exec: + command: + - /bin/sh + - -c + - pg_isready -U {{ .Values.postgres.user }} + initialDelaySeconds: 30 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 3 + readinessProbe: + exec: + command: + - /bin/sh + - -c + - pg_isready -U {{ .Values.postgres.user }} + initialDelaySeconds: 10 + periodSeconds: 5 + timeoutSeconds: 3 + failureThreshold: 2 + resources: + requests: + cpu: {{ .Values.postgres.resources.requests.cpu }} + memory: {{ .Values.postgres.resources.requests.memory }} + limits: + cpu: {{ .Values.postgres.resources.limits.cpu }} + memory: {{ .Values.postgres.resources.limits.memory }} + volumes: + - name: init-sql + configMap: + name: {{ .Release.Name }}-db-schema + volumeClaimTemplates: + - metadata: + name: data + spec: + accessModes: + - {{ .Values.postgres.persistence.accessMode }} + resources: + requests: + storage: {{ .Values.postgres.persistence.size }} + {{- if .Values.postgres.persistence.storageClass }} + storageClassName: {{ .Values.postgres.persistence.storageClass | quote }} + {{- end }} +{{- end }} + diff --git a/aws/my-recipes-chart-aws/templates/frontend-deployment.yaml b/aws/my-recipes-chart-aws/templates/frontend-deployment.yaml new file mode 100644 index 0000000..8973e53 --- /dev/null +++ b/aws/my-recipes-chart-aws/templates/frontend-deployment.yaml @@ -0,0 +1,57 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ .Release.Name }}-{{ .Values.frontend.name }} + namespace: {{ .Values.global.namespace }} + labels: + app: {{ .Release.Name }}-{{ .Values.frontend.name }} + component: frontend +spec: + replicas: {{ .Values.frontend.replicaCount }} + selector: + matchLabels: + app: {{ .Release.Name }}-{{ .Values.frontend.name }} + template: + metadata: + labels: + app: {{ .Release.Name }}-{{ .Values.frontend.name }} + component: frontend + spec: + containers: + - name: {{ .Values.frontend.name }} + image: "{{ .Values.frontend.image.repository }}:{{ .Values.frontend.image.tag }}" + imagePullPolicy: {{ .Values.frontend.image.pullPolicy }} + ports: + - containerPort: {{ .Values.frontend.service.targetPort }} + name: http + protocol: TCP + {{- with .Values.frontend.env }} + env: + {{- range $key, $value := . }} + - name: {{ $key }} + value: {{ $value | quote }} + {{- end }} + {{- end }} + livenessProbe: + httpGet: + path: / + port: http + initialDelaySeconds: 10 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 3 + readinessProbe: + httpGet: + path: / + port: http + initialDelaySeconds: 5 + periodSeconds: 5 + timeoutSeconds: 3 + failureThreshold: 2 + resources: + requests: + cpu: {{ .Values.frontend.resources.requests.cpu }} + memory: {{ .Values.frontend.resources.requests.memory }} + limits: + cpu: {{ .Values.frontend.resources.limits.cpu }} + memory: {{ .Values.frontend.resources.limits.memory }} diff --git a/aws/my-recipes-chart-aws/templates/frontend-service.yaml b/aws/my-recipes-chart-aws/templates/frontend-service.yaml new file mode 100644 index 0000000..9427830 --- /dev/null +++ b/aws/my-recipes-chart-aws/templates/frontend-service.yaml @@ -0,0 +1,17 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ .Release.Name }}-{{ .Values.frontend.name }} + namespace: {{ .Values.global.namespace }} + labels: + app: {{ .Release.Name }}-{{ .Values.frontend.name }} + component: frontend +spec: + type: {{ .Values.frontend.service.type }} + ports: + - port: {{ .Values.frontend.service.port }} + targetPort: {{ .Values.frontend.service.targetPort }} + protocol: TCP + name: http + selector: + app: {{ .Release.Name }}-{{ .Values.frontend.name }} diff --git a/aws/my-recipes-chart-aws/templates/ingress.yaml b/aws/my-recipes-chart-aws/templates/ingress.yaml new file mode 100644 index 0000000..d106c59 --- /dev/null +++ b/aws/my-recipes-chart-aws/templates/ingress.yaml @@ -0,0 +1,89 @@ +{{- if .Values.frontend.ingress.enabled }} +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: {{ .Release.Name }}-frontend + namespace: {{ .Values.global.namespace }} + labels: + app: {{ .Release.Name }}-frontend + component: frontend + {{- with .Values.frontend.ingress.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + {{- if .Values.frontend.ingress.className }} + ingressClassName: {{ .Values.frontend.ingress.className }} + {{- end }} + rules: + {{- range .Values.frontend.ingress.hosts }} + - host: {{ .host | quote }} + http: + paths: + {{- range .paths }} + - path: {{ .path }} + pathType: {{ .pathType }} + backend: + service: + name: {{ $.Release.Name }}-{{ $.Values.frontend.name }} + port: + number: {{ $.Values.frontend.service.port }} + {{- end }} + {{- end }} + {{- if .Values.frontend.ingress.tls }} + tls: + {{- range .Values.frontend.ingress.tls }} + - hosts: + {{- range .hosts }} + - {{ . | quote }} + {{- end }} + secretName: {{ .secretName }} + {{- end }} + {{- end }} +{{- end }} + +--- + +{{- if .Values.backend.ingress.enabled }} +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: {{ .Release.Name }}-backend + namespace: {{ .Values.global.namespace }} + labels: + app: {{ .Release.Name }}-backend + component: backend + {{- with .Values.backend.ingress.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + {{- if .Values.backend.ingress.className }} + ingressClassName: {{ .Values.backend.ingress.className }} + {{- end }} + rules: + {{- range .Values.backend.ingress.hosts }} + - host: {{ .host | quote }} + http: + paths: + {{- range .paths }} + - path: {{ .path }} + pathType: {{ .pathType }} + backend: + service: + name: {{ $.Release.Name }}-{{ $.Values.backend.name }} + port: + number: {{ $.Values.backend.service.port }} + {{- end }} + {{- end }} + {{- if .Values.backend.ingress.tls }} + tls: + {{- range .Values.backend.ingress.tls }} + - hosts: + {{- range .hosts }} + - {{ . | quote }} + {{- end }} + secretName: {{ .secretName }} + {{- end }} + {{- end }} +{{- end }} diff --git a/aws/my-recipes-chart-aws/values.yaml b/aws/my-recipes-chart-aws/values.yaml new file mode 100644 index 0000000..0fafadd --- /dev/null +++ b/aws/my-recipes-chart-aws/values.yaml @@ -0,0 +1,171 @@ +global: + namespace: my-apps + imagePullSecrets: [] + +# Backend configuration +backend: + name: backend + replicaCount: 2 + image: + repository: harbor.dvirlabs.com/my-apps/my-recipes-backend + pullPolicy: IfNotPresent + tag: "latest" + + service: + type: ClusterIP + port: 8000 + targetPort: 8000 + + resources: + requests: + cpu: 100m + memory: 128Mi + limits: + cpu: 500m + memory: 512Mi + + env: + PYTHONUNBUFFERED: "1" + + # Secrets are created in db-secret.yaml + # These are passed via envFrom secretRef + + ingress: + enabled: true + className: "traefik" + annotations: + traefik.ingress.kubernetes.io/router.entrypoints: websecure + traefik.ingress.kubernetes.io/router.tls: "true" + cert-manager.io/cluster-issuer: "letsencrypt-prod" + hosts: + - host: api-my-recipes.dvirlabs.com + paths: + - path: / + pathType: Prefix + tls: + - secretName: api-my-recipes-tls + hosts: + - api-my-recipes.dvirlabs.com + +# Frontend configuration +frontend: + name: frontend + replicaCount: 2 + image: + repository: harbor.dvirlabs.com/my-apps/my-recipes-frontend + pullPolicy: IfNotPresent + tag: "latest" + + service: + type: ClusterIP + port: 80 + targetPort: 80 + + env: + API_BASE: "https://api-my-recipes.dvirlabs.com" + + resources: + requests: + cpu: 50m + memory: 64Mi + limits: + cpu: 200m + memory: 256Mi + + ingress: + enabled: true + className: "traefik" + annotations: + traefik.ingress.kubernetes.io/router.entrypoints: websecure + traefik.ingress.kubernetes.io/router.tls: "true" + cert-manager.io/cluster-issuer: "letsencrypt-prod" + hosts: + - host: my-recipes.dvirlabs.com + paths: + - path: / + pathType: Prefix + tls: + - secretName: my-recipes-tls + hosts: + - my-recipes.dvirlabs.com + externalUrl: "https://my-recipes.dvirlabs.com" + +# PostgreSQL configuration +postgres: + name: db + image: + repository: postgres + tag: "16-alpine" + pullPolicy: IfNotPresent + + user: recipes_user + password: recipes_password + database: recipes_db + port: 5432 + + service: + type: ClusterIP + port: 5432 + targetPort: 5432 + + persistence: + enabled: true + accessMode: ReadWriteOnce + storageClass: "nfs-client" + size: 10Gi + + resources: + requests: + cpu: 100m + memory: 256Mi + limits: + cpu: 1000m + memory: 1Gi + +# OAuth Configuration +oauth: + google: + clientId: "143092846986-hsi59m0on2c9rb5qrdoejfceieao2ioc.apps.googleusercontent.com" + clientSecret: "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" + redirectUri: "https://api-my-recipes.dvirlabs.com/auth/google/callback" + + azure: + clientId: "db244cf5-eb11-4738-a2ea-5b0716c9ec0a" + clientSecret: "Zad8Q~qRBxaQq8up0lLXAq4pHzrVM2JFGFJhHaDp" + tenantId: "consumers" + redirectUri: "https://api-my-recipes.dvirlabs.com/auth/azure/callback" + +# Email Configuration +email: + smtpHost: "smtp.gmail.com" + smtpPort: "587" + smtpUser: "dvirlabs@gmail.com" + smtpPassword: "agaanrhbbazbdytv" + smtpFrom: "dvirlabs@gmail.com" + +# S3 Backup Configuration +s3: + endpoint: "https://s3.amazonaws.com" # Can be overridden for specific regions + accessKey: "" # Set this in project-specific values.yaml + secretKey: "" # Set this in project-specific values.yaml + bucketName: "" # Set this in project-specific values.yaml + region: "us-east-1" # Set this in project-specific values.yaml + backupInterval: "weekly" # Options: test (1 min), daily, weekly + +# Ingress configuration +ingress: + enabled: false # Individual frontend/backend ingress resources handle routing instead + className: "nginx" + annotations: + cert-manager.io/cluster-issuer: "letsencrypt-prod" + hosts: + - host: my-recipes.dvirlabs.com + paths: + - path: / + pathType: Prefix + backend: frontend + tls: + - secretName: recipes-tls + hosts: + - my-recipes.dvirlabs.com + diff --git a/aws/values.yaml b/aws/values.yaml new file mode 100644 index 0000000..f9a5591 --- /dev/null +++ b/aws/values.yaml @@ -0,0 +1,97 @@ +# Project-specific values for AWS EKS deployment +# This file overrides the base values in my-recipes-chart/values.yaml + +global: + namespace: my-apps + +# Backend configuration +backend: + replicaCount: 2 + image: + repository: /my-recipes-backend # Update with your ECR repository + tag: "latest" + + ingress: + className: "alb" + hosts: + - host: api-my-recipes.aws-dvirlabs.com + paths: + - path: / + pathType: Prefix + tls: + - secretName: api-my-recipes-tls + hosts: + - api-my-recipes.aws-dvirlabs.com + +# Frontend configuration +frontend: + replicaCount: 2 + image: + repository: /my-recipes-frontend # Update with your ECR repository + tag: "latest" + + env: + API_BASE: "https://api-my-recipes.aws-dvirlabs.com" + + ingress: + className: "alb" + hosts: + - host: my-recipes.aws-dvirlabs.com + paths: + - path: / + pathType: Prefix + tls: + - secretName: my-recipes-tls + hosts: + - my-recipes.aws-dvirlabs.com + + externalUrl: "https://my-recipes.aws-dvirlabs.com" + +# PostgreSQL configuration +postgres: + # For AWS RDS, set this to use external database + # Leave enabled: true to use in-cluster database + enabled: false # Set to false if using RDS + + # If using RDS, these values are ignored but kept for reference + persistence: + storageClass: "gp3" # EKS default storage class + size: 20Gi + +# OAuth Configuration +oauth: + google: + clientId: "143092846986-hsi59m0on2c9rb5qrdoejfceieao2ioc.apps.googleusercontent.com" + clientSecret: "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" + redirectUri: "https://api-my-recipes.aws-dvirlabs.com/auth/google/callback" + + azure: + clientId: "db244cf5-eb11-4738-a2ea-5b0716c9ec0a" + clientSecret: "Zad8Q~qRBxaQq8up0lLXAq4pHzrVM2JFGFJhHaDp" + tenantId: "consumers" + redirectUri: "https://api-my-recipes.aws-dvirlabs.com/auth/azure/callback" + +# Email Configuration +email: + smtpHost: "smtp.gmail.com" + smtpPort: "587" + smtpUser: "dvirlabs@gmail.com" + smtpPassword: "agaanrhbbazbdytv" + smtpFrom: "dvirlabs@gmail.com" + +# S3 Backup Configuration for AWS +s3: + endpoint: "https://s3.eu-central-1.amazonaws.com" # Update with your region + accessKey: "AKIAXXXXXXXXXXXXXXXX" # Replace with your AWS Access Key + secretKey: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" # Replace with your AWS Secret Key + bucketName: "my-recipes-backups" # Update with your S3 bucket name + region: "eu-central-1" # Update with your region + backupInterval: "weekly" + +# Database connection for AWS RDS (used when postgres.enabled: false) +database: + host: "my-recipes-rds.chw4omcguqv7.eu-central-1.rds.amazonaws.com" + port: "5432" + name: "recipes_db" + user: "recipes_user" + password: "recipes_password" # Store securely in AWS Secrets Manager in production