close
Skip to content

Inkithai/CRM

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 

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

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors