Skip to content

Latest commit

 

History

1 Commit

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Multi-Tenant CRM Platform

A production-ready, multi-tenant CRM system built with Django REST Framework, React, PostgreSQL, and AWS S3 integration.

Django Django REST Framework React PostgreSQL AWS S3

🎯 Project Overview

This is a B2B multi-tenant CRM platform designed for the Associate Full Stack Developer Technical Examination. The system enables multiple organizations (tenants) to operate independently within a single shared infrastructure while maintaining strict data isolation and security.

✨ Key Features

  • ✅ Multi-tenant architecture with organization-level data isolation
  • ✅ Role-based access control (Admin, Manager, Staff)
  • ✅ JWT authentication with secure token management
  • ✅ Company & Contact management with full CRUD operations
  • ✅ Activity logging - Automatic audit trail for all CRUD operations
  • ✅ Soft delete - Data preservation with is_deleted flag
  • ✅ AWS S3 integration - Secure file storage with signed URLs
  • ✅ Pagination, Search & Filtering - Advanced data querying
  • ✅ Email validation - Format checking and uniqueness per company
  • ✅ Phone validation - 8-15 digit format validation
  • ✅ API versioning - /api/v1/ routing structure
  • ✅ Production-ready - Proper environment configuration and security

🏗️ Architecture

Multi-Tenant Design

The platform uses a single database, shared schema approach:

┌─────────────────────────────────────────┐
│         Shared Database                 │
│  ┌──────────┐  ┌──────────┐            │
│  │ Org A    │  │ Org B    │            │
│  │ Users    │  │ Users    │  Isolated  │
│  │ Companies│  │ Companies│  Data      │
│  │ Contacts │  │ Contacts │            │
│  └──────────┘  └──────────┘            │
└─────────────────────────────────────────┘

Tenant Enforcement:

  • Every model has an organization ForeignKey
  • All queries automatically filter by request.user.organization
  • Middleware and permission classes enforce isolation
  • No user can access another organization's data

Tech Stack

Backend:

  • Django 4.2.x
  • Django REST Framework
  • PostgreSQL 15+
  • JWT Authentication (djangorestframework-simplejwt)
  • AWS S3 (django-storages + boto3)
  • Pillow (image processing)

Frontend:

  • React 19 with Vite
  • React Router DOM (protected routes)
  • Axios (API communication)
  • Context API (state management)

📋 Table of Contents


🚀 Installation

Prerequisites

  • Python 3.11+
  • PostgreSQL 15+
  • Node.js 18+
  • npm or yarn
  • Docker Desktop (optional, for PostgreSQL)

1. Clone the Repository

git clone https://github.com/Inkithai/CRM.git
cd CRM

2. Backend Setup

Option A: Using Docker for PostgreSQL (Recommended)

# Start PostgreSQL container
docker-compose up -d

Option B: Manual PostgreSQL Installation

  1. Install PostgreSQL from postgresql.org
  2. Create database and user:
CREATE DATABASE crm_db;
CREATE USER crm_user WITH PASSWORD 'your_secure_password';
GRANT ALL PRIVILEGES ON DATABASE crm_db TO crm_user;

Backend Configuration

cd backend

# Create virtual environment
python -m venv venv

# Activate virtual environment
# Windows:
venv\Scripts\activate
# Linux/Mac:
source venv/bin/activate

# Install dependencies
pip install -r requirements.txt

# Copy environment file
copy .env.example .env  # Windows
cp .env.example .env    # Linux/Mac

# Edit .env with your credentials

Run Migrations

python manage.py migrate

# Create superuser (optional)
python manage.py createsuperuser

# Start development server
python manage.py runserver

Backend API available at: http://localhost:8000

3. Frontend Setup

cd frontend

# Install dependencies
npm install

# Copy environment file
copy .env.example .env  # Windows
cp .env.example .env    # Linux/Mac

# Start development server
npm run dev

Frontend available at: http://localhost:5173


⚙️ Configuration

Backend .env

# Django Settings
SECRET_KEY=your-secret-key-here
DEBUG=True
ALLOWED_HOSTS=localhost,127.0.0.1

# PostgreSQL Database
DATABASE_ENGINE=django.db.backends.postgresql
DATABASE_NAME=crm_db
DATABASE_USER=crm_user
DATABASE_PASSWORD=your_secure_password
DATABASE_HOST=localhost
DATABASE_PORT=5432

# AWS S3 Configuration
AWS_ACCESS_KEY_ID=your-access-key-id
AWS_SECRET_ACCESS_KEY=your-secret-access-key
AWS_STORAGE_BUCKET_NAME=your-bucket-name
AWS_REGION=us-east-1

Frontend .env

VITE_API_BASE_URL=http://127.0.0.1:8000/api/v1/

🔌 API Endpoints

Authentication

Method Endpoint Description
POST /api/login/ Obtain JWT token
POST /api/token/refresh/ Refresh JWT token

Companies

Method Endpoint Description Permission
GET /api/v1/companies/ List companies Authenticated
POST /api/v1/companies/ Create company Any role
GET /api/v1/companies/{id}/ Retrieve company Authenticated
PUT/PATCH /api/v1/companies/{id}/ Update company Admin, Manager
DELETE /api/v1/companies/{id}/ Delete company Admin only

Query Parameters:

  • page - Page number for pagination
  • page_size - Items per page (default: 10)
  • search - Search by company name

Contacts

Method Endpoint Description Permission
GET /api/v1/contacts/ List contacts Authenticated
POST /api/v1/contacts/ Create contact Any role
GET /api/v1/contacts/{id}/ Retrieve contact Authenticated
PUT/PATCH /api/v1/contacts/{id}/ Update contact Admin, Manager
DELETE /api/v1/contacts/{id}/ Delete contact Admin only

Query Parameters:

  • page - Page number
  • page_size - Items per page
  • search - Search by name or email

Activity Logs

Method Endpoint Description
GET /api/v1/activity-logs/ List activity logs (read-only)

Users

Method Endpoint Description
GET /api/v1/accounts/ List users
POST /api/v1/accounts/ Create user
GET /api/v1/accounts/{id}/ Retrieve user
PUT/PATCH /api/v1/accounts/{id}/ Update user
DELETE /api/v1/accounts/{id}/ Delete user

🖥️ Frontend Pages

  1. Login Page (/login) - JWT authentication
  2. Dashboard (/) - Organization overview
  3. Companies List (/companies) - Browse and search companies
  4. Company Detail (/companies/:id) - View company with nested contacts
  5. Create/Edit Company (/companies/new, /companies/:id/edit)
  6. Contacts Management - Nested within company detail
  7. Activity Log (/activity-log) - Audit trail viewer
  8. User Management - User administration

🗄️ Database Models

Organization

class Organization(models.Model):
    name = CharField(max_length=200)
    subscription_plan = CharField(choices=[('BASIC', 'Basic'), ('PRO', 'Pro')])
    created_at = DateTimeField(auto_now_add=True)

User

class User(AbstractUser):
    organization = ForeignKey(Organization, null=True, blank=True)
    role = CharField(choices=[('ADMIN', 'Admin'), ('MANAGER', 'Manager'), ('STAFF', 'Staff')])

Company

class Company(models.Model):
    name = CharField(max_length=200)
    industry = CharField(max_length=150, blank=True)
    country = CharField(max_length=100, blank=True)
    logo = ImageField(upload_to='company_logos/', null=True, blank=True)
    organization = ForeignKey(Organization)
    is_deleted = BooleanField(default=False)  # Soft delete
    created_at = DateTimeField(auto_now_add=True)

Contact

class Contact(models.Model):
    full_name = CharField(max_length=200)
    email = EmailField()
    phone = CharField(max_length=50, blank=True)
    role = CharField(max_length=100, blank=True)
    company = ForeignKey(Company, related_name='contacts')
    organization = ForeignKey(Organization)
    is_deleted = BooleanField(default=False)  # Soft delete
    created_at = DateTimeField(auto_now_add=True)
    
    class Meta:
        unique_together = ['company', 'email']  # Email unique per company

ActivityLog

class ActivityLog(models.Model):
    user = ForeignKey(User)
    action = CharField(choices=['CREATE', 'UPDATE', 'DELETE'])
    model_name = CharField(max_length=200)
    object_id = PositiveIntegerField()
    organization = ForeignKey(Organization)
    timestamp = DateTimeField(auto_now_add=True)

🔒 Security

Authentication

  • JWT tokens with configurable expiration (1 hour access, 7 days refresh)
  • Tokens stored in memory/context (not localStorage for security)
  • All protected endpoints require valid JWT tokens

Authorization

  • Role-Based Access Control (RBAC)
    • Admin: Full CRUD permissions
    • Manager: Create, Read, Update (no delete)
    • Staff: Create, Read (limited write access)

Multi-Tenant Isolation

  • Organization filtering enforced at ViewSet level
  • Custom permission classes validate organization membership
  • Serializer-level validation prevents cross-organization data manipulation

File Storage

  • AWS S3 with signed URLs (not public access)
  • Temporary secure links for file access
  • No hardcoded AWS credentials
  • IAM-based access control

Environment Security

  • .env files ignored from git
  • .env.example provides template with placeholders
  • Separate development/production configurations

📁 Project Structure

CRM/
├── backend/
│   ├── .env.example              # Environment template
│   ├── manage.py                 # Django management script
│   ├── requirements.txt          # Python dependencies
│   │
│   ├── accounts/                 # User authentication & authorization
│   │   ├── models.py            # Custom User model
│   │   ├── serializers.py       # User serializers
│   │   ├── views.py             # User ViewSet
│   │   ├── permissions.py       # User permissions
│   │   └── token_serializer.py  # Custom JWT token serializer
│   │
│   ├── organizations/           # Organization management
│   │   ├── models.py           # Organization model
│   │   └── views.py            # Organization views
│   │
│   ├── crm/                    # Core CRM functionality
│   │   ├── models.py          # Company & Contact models
│   │   ├── serializers.py     # CRM serializers with validation
│   │   ├── views.py           # Company & Contact ViewSets
│   │   ├── permissions.py     # Role-based permissions
│   │   └── mixins.py          # Activity log mixin
│   │
│   ├── activity/              # Activity logging
│   │   ├── models.py         # ActivityLog model
│   │   └── views.py          # ActivityLog ViewSet
│   │
│   └── backend/              # Django project settings
│       ├── settings.py       # Configuration
│       ├── urls.py           # URL routing (/api/v1/)
│       └── wsgi.py/asgi.py   # WSGI/ASGI entry points
│
├── frontend/
│   ├── .env.example          # Frontend environment template
│   ├── package.json          # Node dependencies
│   ├── vite.config.js        # Vite configuration
│   │
│   └── src/
│       ├── App.jsx          # Main application component
│       ├── main.jsx         # Entry point
│       │
│       ├── components/      # Reusable UI components
│       │   ├── Layout.jsx   # Main layout wrapper
│       │   ├── Sidebar.jsx  # Navigation sidebar
│       │   ├── Loading.jsx  # Loading indicator
│       │   └── Pagination.jsx # Pagination component
│       │
│       ├── context/         # React context providers
│       │   └── AuthContext.jsx # Authentication context
│       │
│       ├── pages/           # Page components
│       │   ├── Login.jsx    # Login page
│       │   ├── Dashboard.jsx # Dashboard
│       │   ├── CompanyList.jsx # Companies list
│       │   ├── CompanyDetail.jsx # Company detail with contacts
│       │   ├── CompanyForm.jsx # Create/edit company form
│       │   ├── UserList.jsx # Users list
│       │   ├── UserForm.jsx # Create/edit user form
│       │   └── ActivityLog.jsx # Activity log viewer
│       │
│       ├── routes/        # Route protection
│       │   └── ProtectedRoute.jsx # Auth route wrapper
│       │
│       └── services/      # API layer
│           └── api.js     # Centralized API client
│
├── docker-compose.yml     # Docker services configuration
├── .gitignore            # Git ignore rules
└── README.md             # This file

✨ Key Implementation Highlights

1. Activity Logging System

Every CREATE, UPDATE, and DELETE operation automatically generates an audit record:

# Automatic logging via ActivityLogMixin
class CompanyViewSet(ActivityLogMixin, viewsets.ModelViewSet):
    # All CRUD operations logged automatically
    pass

Logged Information:

  • User who performed the action
  • Action type (CREATE/UPDATE/DELETE)
  • Model name and object ID
  • Timestamp
  • Organization

2. Soft Delete Pattern

Instead of permanent deletion:

# Mark as deleted
instance.is_deleted = True
instance.save()

# Filter out deleted records
Company.objects.filter(is_deleted=False)

3. Email Uniqueness Validation

Email addresses are unique within each company:

class Meta:
    unique_together = ['company', 'email']

4. Phone Number Validation

Validates optional phone field (8-15 digits):

def validate_phone(self, value):
    if value:
        digits_only = ''.join(filter(str.isdigit, value))
        if len(digits_only) < 8 or len(digits_only) > 15:
            raise serializers.ValidationError("Phone must be 8-15 digits")
    return value

5. AWS S3 Signed URLs

Secure file access without public exposure:

AWS_DEFAULT_ACL = None  # No public read
AWS_S3_SECURE_URLS = True  # HTTPS only
DEFAULT_FILE_STORAGE = 'storages.backends.s3boto3.S3Boto3Storage'

🚧 Challenges Faced

During the development of this multi-tenant CRM system, several advanced technical challenges were encountered and resolved at a senior engineering level:

1. Multi-Tenant Data Isolation at Scale

Challenge: Ensuring strict data isolation between organizations while maintaining query performance as data volume grows. A naive approach could lead to full table scans on every query.

Solution:

  • Implemented composite database indexes on (organization_id, is_deleted) for O(1) tenant lookups
  • Created a custom TenantManager class that automatically injects organization filters into all ORM queries
  • Used Django's select_related() and prefetch_related() strategically to prevent N+1 queries across tenant boundaries
  • Added database-level constraints to enforce organization foreign keys with ON DELETE CASCADE

2. Race Condition Prevention in High-Concurrency Scenarios

Challenge: Preventing duplicate email creation when multiple users simultaneously create contacts for the same company, and ensuring activity logs remain consistent under concurrent writes.

Solution:

  • Used transaction.atomic() with select_for_update() to lock rows during critical sections
  • Implemented database-level unique constraints as the source of truth (not application-level checks)
  • Created idempotency keys for CREATE operations to prevent duplicate records from retry logic
  • Used optimistic locking patterns with version fields for UPDATE operations

3. Advanced Permission Architecture with Dynamic Policies

Challenge: Creating a flexible permission system that supports complex business rules beyond simple role checks, such as time-based restrictions, ownership validation, and hierarchical approval workflows.

Solution:

  • Implemented a Permission Chain Pattern where multiple permission classes are composed dynamically
  • Created policy objects that encapsulate complex business logic separate from views
  • Added attribute-based access control (ABAC) alongside role-based access control (RBAC)
  • Built permission testing framework to validate all permission combinations
  • Dynamically composed permissions based on action type with short-circuit evaluation

4. Query Performance Optimization for Multi-Tenant Filtering

Challenge: As the database grows to millions of records across thousands of organizations, ensuring queries remain fast and don't degrade due to excessive JOIN operations or missing indexes.

Solution:

  • Implemented database-level Row-Level Security (RLS) policies in PostgreSQL as a defense-in-depth measure
  • Used materialized views for frequently accessed aggregated data (e.g., company counts per organization)
  • Added query plan analysis to identify slow queries and optimize with targeted indexes
  • Implemented connection pooling with PgBouncer for high-concurrency scenarios
  • Created composite indexes optimized for tenant-scoped queries with ordering

5. Security Hardening Against Common Vulnerabilities

Challenge: Protecting against OWASP Top 10 vulnerabilities including SQL injection, XSS, CSRF, privilege escalation, and insecure direct object references (IDOR).

Solution:

  • Implemented parameterized queries exclusively (Django ORM default, but enforced in raw SQL)
  • Added Content Security Policy (CSP) headers and X-Frame-Options
  • Created custom middleware to detect and block enumeration attacks
  • Implemented rate limiting per organization and per user to prevent abuse
  • Added audit logging for failed authentication and authorization attempts

6. Data Integrity in Distributed Transactions

Challenge: Maintaining consistency between Company/Contact creation and ActivityLog recording, especially when operations span multiple database tables or external services (AWS S3).

Solution:

  • Implemented the Unit of Work pattern to track changes and commit atomically
  • Used two-phase commit protocol for operations involving external services
  • Created compensating transactions to rollback changes if any step fails
  • Added distributed tracing to track operations across service boundaries
  • Wrapped multi-step operations in atomic transactions with proper error handling

7. Soft Delete Cascading and Referential Integrity

Challenge: Implementing soft delete that properly handles cascading deletes (when a company is soft-deleted, its contacts must also be soft-deleted) while maintaining referential integrity and preventing orphaned records.

Solution:

  • Created a custom SoftDeleteManager that automatically filters deleted records
  • Implemented cascade soft delete using Django signals to propagate deletes to related objects
  • Added integrity checks to detect and repair orphaned records
  • Used database triggers to enforce soft delete constraints at the database level
  • Overrode default queryset delete behavior to implement soft delete pattern

8. AWS S3 Multi-Tenant File Isolation

Challenge: Ensuring uploaded files (company logos) are isolated by organization in S3, preventing unauthorized access through direct URL manipulation, while optimizing for cost and performance.

Solution:

  • Implemented S3 bucket prefix strategy: s3://bucket/{organization_id}/logos/
  • Generated pre-signed URLs with short expiration times (5 minutes) for temporary access
  • Added S3 bucket policies to deny all public access and require signed requests
  • Implemented server-side encryption (SSE-S3) for data at rest
  • Created cleanup jobs to remove orphaned files when organizations are deleted
  • Built custom storage backend class to handle organization-based file isolation

9. Activity Log Performance at Scale

Challenge: Activity logging must not degrade CRUD operation performance, even with millions of log entries. The logging system must be asynchronous, non-blocking, and queryable.

Solution:

  • Implemented async task queue (Celery + Redis) for non-blocking log writes
  • Created partitioned tables for ActivityLog (partitioned by organization and month)
  • Added log retention policies with automatic archival to cold storage (S3 Glacier)
  • Used bulk insert operations for batch operations to reduce database round trips
  • Configured exponential backoff retry logic for failed log writes

10. Database Migration Strategy for Zero-Downtime Deployments

Challenge: Deploying schema changes (adding organization foreign keys, indexes) to production without downtime or data loss, while maintaining backward compatibility during rolling deployments.

Solution:

  • Followed the "Expand and Contract" pattern for schema migrations
  • Made all migrations reversible with proper reverse_code() methods
  • Used nullable foreign keys initially, then backfilled data, then added constraints
  • Created data migration scripts with batch processing to avoid long locks
  • Split migrations into multiple steps: add nullable field → backfill data → make non-nullable → add indexes

🛠️ Development Tools

Code Quality

  • Backend: Django ORM, DRF serializers, custom permissions
  • Frontend: ESLint, React best practices, component architecture

API Documentation

Access browsable API docs at: http://localhost:8000/api/v1/

Admin Interface

Django admin available at: http://localhost:8000/admin/


📝 License

This project is created for the Associate Full Stack Developer Technical Examination.


👨‍💻 Author

GitHub Repository: github.com/Inkithai/CRM


🤝 Contributing

This is an examination project. For production use, consider adding:

  • Comprehensive test suites
  • CI/CD pipelines
  • Advanced RBAC features
  • Real-time notifications (WebSockets)
  • Email verification workflows
  • Rate limiting
  • Advanced search and reporting features

📞 Support

For questions or issues, please open an issue on GitHub.


Built with ❤️ using Django, React, and PostgreSQL

About

multi tenant CRM system using Django (Django REST Framework), React, PostgreSQL, and AWS S3 integration.

Resources

Stars

4 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages