Skip to content
Merged

Dev #14

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
171 changes: 171 additions & 0 deletions .github/workflows/ci_cd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,177 @@ jobs:
# docker image prune -af
# docker volume prune -f

# deploy:
# needs: setup-and-test
# if: success() && github.event_name == 'push' && github.ref == 'refs/heads/main'
# runs-on: self-hosted
# permissions: write-all
# steps:
# - name: Pre-checkout cleanup
# run: |
# # Stop any Python processes
# sudo pkill -f python || true

# # Remove problematic __pycache__ directories
# sudo find /home/azureuser/actions-runner/_work/Mapapi/Mapapi -type d -name "__pycache__" -exec rm -rf {} + || true

# # Force remove the entire directory if needed
# sudo rm -rf /home/azureuser/actions-runner/_work/Mapapi/Mapapi || true

# # Recreate directory with proper permissions
# sudo mkdir -p /home/azureuser/actions-runner/_work/Mapapi/Mapapi
# sudo chown -R $USER:$USER /home/azureuser/actions-runner/_work/Mapapi/Mapapi
# sudo chmod -R 777 /home/azureuser/actions-runner/_work/Mapapi/Mapapi

# - name: Checkout Repository
# uses: actions/checkout@v3

# - name: Login to Docker Hub
# uses: docker/login-action@v2
# with:
# username: ${{ secrets.DOCKER_USERNAME }}
# password: ${{ secrets.DOCKER_PASSWORD }}

# - name: Create entrypoint.sh
# run: |
# cat > entrypoint.sh << 'EOL'
# #!/bin/sh
# set -e

# # Wait for postgres to be ready
# python manage.py wait_for_db

# # Apply database migrations
# python manage.py migrate

# # Create superuser if it doesn't exist
# python manage.py createsuperuser --noinput || true

# # Start server
# exec python manage.py runserver 0.0.0.0:8000
# EOL
# shell: bash
# continue-on-error: false

# - name: Create .env file
# run: |
# {
# echo "ALLOWED_HOSTS=${{ secrets.ALLOWED_HOSTS }}"
# echo "ANDROID_CLIENT_ID=${{ secrets.ANDROID_CLIENT_ID }}"
# echo "DB_HOST=${{ secrets.DB_HOST }}"
# echo "DJANGO_SUPERUSER_EMAIL=${{ secrets.DJANGO_SUPERUSER_EMAIL }}"
# echo "DJANGO_SUPERUSER_FIRST_NAME=${{ secrets.DJANGO_SUPERUSER_FIRST_NAME }}"
# echo "DJANGO_SUPERUSER_LAST_NAME=${{ secrets.DJANGO_SUPERUSER_LAST_NAME }}"
# echo "DJANGO_SUPERUSER_PASSWORD=${{ secrets.DJANGO_SUPERUSER_PASSWORD }}"
# echo "DJANGO_SUPERUSER_USERNAME=${{ secrets.DJANGO_SUPERUSER_USERNAME }}"
# echo "IOS_CLIENT_ID=${{ secrets.IOS_CLIENT_ID }}"
# echo "PORT=${{ secrets.PORT }}"
# echo "POSTGRES_USER=${{ secrets.POSTGRES_USER }}"
# echo "POSTGRES_PASSWORD=${{ secrets.POSTGRES_PASSWORD }}"
# echo "POSTGRES_DB=${{ secrets.POSTGRES_DB }}"
# echo "SECRET_KEY=${{ secrets.SECRET_KEY }}"
# echo "WEB_CLIENT_ID=${{ secrets.WEB_CLIENT_ID }}"
# echo "WEB_CLIENT_SECRET=${{ secrets.WEB_CLIENT_SECRET }}"
# echo "TWILIO_ACCOUNT_SID=${{ secrets.TWILIO_ACCOUNT_SID }}"
# echo "TWILIO_AUTH_TOKEN=${{ secrets.TWILIO_AUTH_TOKEN }}"
# echo "TWILIO_PHONE_NUMBER=${{ secrets.TWILIO_PHONE_NUMBER }}"
# } > .env

# - name: Build and Run Docker Compose
# env:
# ALLOWED_HOSTS: ${{ secrets.ALLOWED_HOSTS }}
# ANDROID_CLIENT_ID: ${{ secrets.ANDROID_CLIENT_ID }}
# DB_HOST: ${{ secrets.DB_HOST }}
# DJANGO_SUPERUSER_EMAIL: ${{ secrets.DJANGO_SUPERUSER_EMAIL }}
# DJANGO_SUPERUSER_FIRST_NAME: ${{ secrets.DJANGO_SUPERUSER_FIRST_NAME }}
# DJANGO_SUPERUSER_LAST_NAME: ${{ secrets.DJANGO_SUPERUSER_LAST_NAME }}
# DJANGO_SUPERUSER_PASSWORD: ${{ secrets.DJANGO_SUPERUSER_PASSWORD }}
# DJANGO_SUPERUSER_USERNAME: ${{ secrets.DJANGO_SUPERUSER_USERNAME }}
# IOS_CLIENT_ID: ${{ secrets.IOS_CLIENT_ID }}
# PORT: ${{ secrets.PORT }}
# POSTGRES_USER: ${{ secrets.POSTGRES_USER }}
# POSTGRES_PASSWORD: ${{ secrets.POSTGRES_PASSWORD }}
# POSTGRES_DB: ${{ secrets.POSTGRES_DB }}
# SECRET_KEY: ${{ secrets.SECRET_KEY }}
# TEST_POSTGRES_DB: ${{ secrets.TEST_POSTGRES_DB }}
# WEB_CLIENT_ID: ${{ secrets.WEB_CLIENT_ID }}
# WEB_CLIENT_SECRET: ${{ secrets.WEB_CLIENT_SECRET }}
# TWILIO_ACCOUNT_SID: ${{ secrets.TWILIO_ACCOUNT_SID }}
# TWILIO_AUTH_TOKEN: ${{ secrets.TWILIO_AUTH_TOKEN }}
# TWILIO_PHONE_NUMBER: ${{ secrets.TWILIO_PHONE_NUMBER }}
# run: |
# # Conditionally remove existing network if it exists
# docker network ls | grep -q mapapi_micro-services-network && docker network rm mapapi_micro-services-network || true

# # Build and run Docker Compose
# docker-compose -f _cd_pipeline.yml up --build -d

# - name: Post-deployment cleanup
# if: always()
# run: |
# # Clean up dangling volumes and images
# docker system prune -af --volumes
# docker image prune -af
# docker volume prune -f

deploy:
<<<<<<< HEAD
needs: setup-and-test
if: success() && github.event_name == 'push' && github.ref == 'refs/heads/main'
runs-on: ubuntu-latest

steps:
- name: Set up SSH connection
run: |
mkdir -p ~/.ssh
echo "${{ secrets.EC2_SSH_KEY }}" > ~/.ssh/id_rsa
chmod 600 ~/.ssh/id_rsa
ssh-keyscan -H 13.36.39.58 >> ~/.ssh/known_hosts

- name: Deploy to EC2 instance
run: |
ssh ec2-user@13.36.39.58 << 'EOF'
cd ~/app

if [ ! -d ".git" ]; then
git clone https://github.com/223MapAction/Mapapi.git .
fi

git pull origin main

echo "Création du fichier .env..."
cat > .env <<EOL
ALLOWED_HOSTS: ${{ secrets.ALLOWED_HOSTS }}
ANDROID_CLIENT_ID: ${{ secrets.ANDROID_CLIENT_ID }}
DB_HOST: ${{ secrets.DB_HOST }}
DJANGO_SUPERUSER_EMAIL: ${{ secrets.DJANGO_SUPERUSER_EMAIL }}
DJANGO_SUPERUSER_FIRST_NAME: ${{ secrets.DJANGO_SUPERUSER_FIRST_NAME }}
DJANGO_SUPERUSER_LAST_NAME: ${{ secrets.DJANGO_SUPERUSER_LAST_NAME }}
DJANGO_SUPERUSER_PASSWORD: ${{ secrets.DJANGO_SUPERUSER_PASSWORD }}
DJANGO_SUPERUSER_USERNAME: ${{ secrets.DJANGO_SUPERUSER_USERNAME }}
IOS_CLIENT_ID: ${{ secrets.IOS_CLIENT_ID }}
PORT: ${{ secrets.PORT }}
POSTGRES_USER: ${{ secrets.POSTGRES_USER }}
POSTGRES_PASSWORD: ${{ secrets.POSTGRES_PASSWORD }}
POSTGRES_DB: ${{ secrets.POSTGRES_DB }}
SECRET_KEY: ${{ secrets.SECRET_KEY }}
TEST_POSTGRES_DB: ${{ secrets.TEST_POSTGRES_DB }}
WEB_CLIENT_ID: ${{ secrets.WEB_CLIENT_ID }}
WEB_CLIENT_SECRET: ${{ secrets.WEB_CLIENT_SECRET }}
TWILIO_ACCOUNT_SID: ${{ secrets.TWILIO_ACCOUNT_SID }}
TWILIO_AUTH_TOKEN: ${{ secrets.TWILIO_AUTH_TOKEN }}
TWILIO_PHONE_NUMBER: ${{ secrets.TWILIO_PHONE_NUMBER }}
SUPABASE_URL: ${{ secrets.SUPABASE_URL }}
SUPABASE_ANON_KEY: ${{ secrets.SUPABASE_ANON_KEY }}
USE_SUPABASE_STORAGE: ${{ secrets.USE_SUPABASE_STORAGE }}
EMAIL_HOST: ${{ secrets.EMAIL_HOST }}
EMAIL_HOST_USER: ${{ secrets.EMAIL_HOST_USER }}
EMAIL_HOST_PASSWORD: ${{ secrets.EMAIL_HOST_PASSWORD }}
EOL

./deploy.sh
EOF
=======
needs: cleanup
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
runs-on: self-hosted
Expand Down Expand Up @@ -290,3 +460,4 @@ jobs:
docker system prune -af --volumes
docker image prune -af
docker volume prune -f
>>>>>>> f2c8cfb682a3a605275df0fab503112a426fc0c7
21 changes: 21 additions & 0 deletions Mapapi/middleware.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
from django.utils.deprecation import MiddlewareMixin
from .models import Organisation

class OrganisationFromSubdomainMiddleware(MiddlewareMixin):
"""
Middleware pour extraire le sous-domaine de la requête et attacher l'organisation à request.organisation
"""
def process_request(self, request):
host = request.get_host().split(':')[0] # retire le port éventuel
# On suppose que le domaine principal est map-action.com
# et que le sous-domaine correspond à l'organisation
parts = host.split('.')
if len(parts) < 2:
request.organisation = None
return
subdomain = parts[0]
try:
organisation = Organisation.objects.get(subdomain=subdomain)
request.organisation = organisation
except Organisation.DoesNotExist:
request.organisation = None
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# Generated by Django 4.2.7 on 2025-07-17 10:21

from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):

dependencies = [
('Mapapi', '0010_organisationtag'),
]

operations = [
migrations.CreateModel(
name='Organisation',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=255, unique=True)),
('is_premium', models.BooleanField(default=False)),
('subdomain', models.CharField(max_length=255, unique=True)),
('logo_url', models.URLField(blank=True, null=True)),
('primary_color', models.CharField(default='#4CAF50', max_length=7)),
('secondary_color', models.CharField(default='#8BC34A', max_length=7)),
('background_color', models.CharField(default='#F0F0F0', max_length=7)),
('created_at', models.DateTimeField(auto_now_add=True)),
],
),
migrations.AlterField(
model_name='evenement',
name='audio',
field=models.FileField(blank=True, null=True, upload_to='events/'),
),
migrations.AlterField(
model_name='evenement',
name='photo',
field=models.ImageField(blank=True, null=True, upload_to='events/'),
),
migrations.AlterField(
model_name='evenement',
name='video',
field=models.FileField(blank=True, null=True, upload_to='events/'),
),
migrations.AlterField(
model_name='incident',
name='audio',
field=models.FileField(blank=True, null=True, upload_to='incidents/'),
),
migrations.AlterField(
model_name='incident',
name='photo',
field=models.ImageField(blank=True, null=True, upload_to='incidents/'),
),
migrations.AlterField(
model_name='incident',
name='video',
field=models.FileField(blank=True, null=True, upload_to='incidents/'),
),
migrations.AlterField(
model_name='rapport',
name='file',
field=models.FileField(blank=True, null=True, upload_to='reports/'),
),
migrations.AlterField(
model_name='zone',
name='photo',
field=models.ImageField(blank=True, null=True, upload_to='zones/'),
),
migrations.AlterField(
model_name='user',
name='organisation',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='users', to='Mapapi.organisation'),
),
]
23 changes: 21 additions & 2 deletions Mapapi/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,20 @@
)


# Modèle d'organisation pour gérer les organisations liées aux utilisateurs
class Organisation(models.Model):
name = models.CharField(max_length=255, unique=True)
is_premium = models.BooleanField(default=False)
subdomain = models.CharField(max_length=255, unique=True) # ex: wetlands
logo_url = models.URLField(null=True, blank=True)
primary_color = models.CharField(max_length=7, default="#4CAF50") # hex
secondary_color = models.CharField(max_length=7, default="#8BC34A")
background_color = models.CharField(max_length=7, default="#F0F0F0")
created_at = models.DateTimeField(auto_now_add=True)

def __str__(self):
return self.name

# Creation du model User pour les utilisateurs de l'application pour securiser l'entree des commandes

class UserManager(BaseUserManager):
Expand Down Expand Up @@ -146,8 +160,13 @@ class User(AbstractBaseUser, PermissionsMixin):
community = models.ForeignKey('Communaute', db_column='user_communaute_id', related_name='user_communaute',
on_delete=models.CASCADE, null=True, blank=True)
provider = models.CharField(_('provider'), max_length=255, blank=True, null=True)
organisation = models.CharField(max_length=255, blank=True,
null=True)
organisation = models.ForeignKey(
Organisation,
on_delete=models.SET_NULL,
null=True,
blank=True,
related_name="users"
)
points = models.IntegerField(null=True, blank=True, default=0)
zones = models.ManyToManyField('Zone', blank=True)
verification_token = models.UUIDField(default=uuid.uuid4, editable=False, null=True, blank=True)
Expand Down
6 changes: 6 additions & 0 deletions Mapapi/serializer.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
from rest_framework import serializers, generics, permissions, status
from .models import *

class OrganisationSerializer(serializers.ModelSerializer):
class Meta:
model = Organisation
fields = '__all__'

from rest_framework import serializers
from django.contrib.auth import authenticate
from rest_framework.serializers import ModelSerializer
Expand Down
3 changes: 3 additions & 0 deletions Mapapi/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@
from .views import PasswordResetView

urlpatterns = [
path('tenant-config/', TenantConfigView.as_view(), name='tenant_config'),
path('organisations/', OrganisationViewSet.as_view(), name='organisation-list-create'),
path('organisations/<int:pk>', OrganisationViewSet.as_view(), name='organisation-detail'),
# URL PATTERNS for the documentation
path('api/schema/', SpectacularAPIView.as_view(), name='schema'),
# Optional UI:
Expand Down
33 changes: 33 additions & 0 deletions Mapapi/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,39 @@ def get_random(length=6):

logger = logging.getLogger(__name__)

from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
from .models import Organisation
from rest_framework import permissions, generics

class OrganisationViewSet(generics.ListCreateAPIView, generics.RetrieveUpdateDestroyAPIView):
queryset = Organisation.objects.all()
serializer_class = OrganisationSerializer
permission_classes = []

def get_queryset(self):
# Optionnel : filtrer selon les droits de l'utilisateur
return Organisation.objects.all()

class TenantConfigView(APIView):
permission_classes = [] # Accessible sans authentification, car utilisé pour personnaliser le front dès le login

def get(self, request, format=None):
org = getattr(request, 'organisation', None)
if org is None:
return Response({'detail': 'Organisation not found for this subdomain.'}, status=status.HTTP_404_NOT_FOUND)
data = {
'name': org.name,
'subdomain': org.subdomain,
'logo_url': org.logo_url,
'primary_color': org.primary_color,
'secondary_color': org.secondary_color,
'background_color': org.background_color,
'is_premium': org.is_premium,
}
return Response(data)

N = 7

def get_random():
Expand Down
1 change: 1 addition & 0 deletions backend/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'Mapapi.middleware.OrganisationFromSubdomainMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pip
Loading