Chat about this codebase

AI-powered code exploration

Online

Project Overview

This Netflix clone delivers a full-stack, AI-powered chat experience for discovering and discussing movies. Developers get hands-on exposure to GPT-3.5 integration, secure API design, and a modern Angular frontend—all in one project.

Value Proposition

  • Enable conversational movie discovery with OpenAI’s GPT-3.5
  • Store and manage user-bot conversations for personalized experiences
  • Protect endpoints with JWT authentication and rate limiting
  • Learn end-to-end development: from ASP.NET Core APIs to Angular UI

Main Features

  • AI Chatbot: Ask questions and get contextual movie suggestions
  • Personalized Recommendations: Tailor results based on chat history
  • Conversation History: Persist and retrieve past dialogs
  • Authentication & Security: JWT-based login, role management
  • Rate Limiting: Prevent abuse of AI and recommendation endpoints
  • Responsive Frontend: Global chat interface, history panel, suggestion cards

Tech Stack

  • Backend
    • ASP.NET Core Web API (.NET 6+)
    • Entity Framework Core (SQL Server)
    • OpenAI GPT-3.5 via HttpClient
    • JWT Authentication, Middleware for rate limiting
  • Frontend
    • Angular 13+ with Angular CLI
    • TypeScript, RxJS, Angular Material
  • Development Tools
    • Visual Studio / VS Code
    • Postman or HTTPie for API testing
    • Docker (optional containerization)

Quick Start Commands

# Backend: run API on http://localhost:5000
cd NetflixClone.Api
dotnet restore
dotnet run

# Frontend: run UI on http://localhost:4200
cd NetflixClone.Client
npm install
ng serve

When to Use This Project

  • Prototype AI-driven chat interfaces for media platforms
  • Learn secure, scalable API design with ASP.NET Core
  • Explore real-world GPT-3.5 integrations and state management
  • Bootstrap a movie recommendation or chatbot MVP for internal tools

Getting Started

Follow these steps to run the Netflix clone locally with seeded data.

Prerequisites

  • .NET 7 SDK
  • SQL Server (LocalDB or full instance)
  • Node.js ≥ 16
  • Angular CLI (npm install -g @angular/cli)

1. Clone the Repository

git clone https://github.com/Moaz-93/Netflix_Clone.git
cd Netflix_Clone

2. Backend Setup

2.1 Configure Connection String

Edit Netflix.API/appsettings.Development.json (or appsettings.json) to point at your SQL Server:

{
  "ConnectionStrings": {
    "DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=NetflixCloneDb;Trusted_Connection=True;MultipleActiveResultSets=true"
  }
}

2.2 Enable Data Seeding

Ensure Program.cs invokes the seeder before app.Run():

using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using Netflix.API.Data;

var builder = WebApplication.CreateBuilder(args);
// Register DbContext and Identity
builder.Services.AddDbContext<ApplicationDbContext>(options =>
    options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));
builder.Services.AddDefaultIdentity<ApplicationUser>(options => {/*...*/})
    .AddRoles<IdentityRole>()
    .AddEntityFrameworkStores<ApplicationDbContext>();

var app = builder.Build();

// Migrate and seed
using var scope = app.Services.CreateScope();
var services = scope.ServiceProvider;
var db = services.GetRequiredService<ApplicationDbContext>();
var userMgr = services.GetRequiredService<UserManager<ApplicationUser>>();
var roleMgr = services.GetRequiredService<RoleManager<IdentityRole>>();

await db.Database.MigrateAsync();
await ApplicationDbContextSeed.SeedAsync(db, userMgr, roleMgr);

app.MapControllers();
app.Run();

2.3 Build and Run

cd Netflix.API
dotnet build
dotnet run

The API listens on https://localhost:7140 and auto-seeds roles, admin user, plans and categories.

3. Frontend Setup

3.1 Verify API URL

Netflix/src/environments/environment.ts already points to the local API:

export const environment = {
  production: false,
  apiUrl: 'https://localhost:7140/api'
};

3.2 Install Dependencies & Serve

cd Netflix
npm install
npm start

The Angular app serves at http://localhost:4200 and proxies API calls to https://localhost:7140/api.

4. Verify Installation

  • Browse Swagger: https://localhost:7140/swagger
  • Log in as seeded admin:
    • Email: admin@netflix.com
    • Password: Pa$$w0rd!
  • Visit the UI: create account, browse movies or categories.

You now have a full local Netflix clone with backend, frontend and seed data ready for development.

Core Concepts & Data Models

This section outlines the primary domain entities, their key properties, and relationships. Understanding these models helps you navigate user management, content operations, subscriptions, payments, and recommendations.

1. Users & Profiles

ApplicationUser

Extends ASP-NET Identity to track OTP state, email verification, roles, and relations.

public class ApplicationUser : IdentityUser
{
    public string FullName { get; set; }
    public bool IsAdmin { get; set; } = false;

    // OTP-based email verification
    public string? OtpCode { get; set; }
    public DateTime? OtpExpiry { get; set; }
    public bool IsEmailVerified { get; set; } = false;

    // Navigation
    public ICollection<Profile> Profiles { get; set; } = new List<Profile>();
    public ICollection<UserSubscription> Subscriptions { get; set; } = new List<UserSubscription>();
}

Profile

Represents a user’s viewing profile (e.g., “Kids”, “John”).

public class Profile
{
    public int Id { get; set; }
    public string Name { get; set; }          // e.g. “John’s Profile”
    public DateTime CreatedAt { get; set; }   // UTC
    public string UserId { get; set; }
    public ApplicationUser User { get; set; }

    // Favorites for this profile
    public ICollection<Favorite> Favorites { get; set; } = new List<Favorite>();
}

2. Subscriptions

SubscriptionPlan

Defines pricing and duration of each plan.

public class SubscriptionPlan
{
    public int Id { get; set; }
    public string Name { get; set; }          // e.g. “Premium”
    public decimal Price { get; set; }        // Monthly price
    public int DurationDays { get; set; }     // e.g. 30
}

UserSubscription

Links a user to an active or past plan.

public class UserSubscription
{
    public int Id { get; set; }
    public string UserId { get; set; }
    public ApplicationUser User { get; set; }

    public int SubscriptionPlanId { get; set; }
    public SubscriptionPlan Plan { get; set; }

    public DateTime StartDate { get; set; }   // UTC
    public DateTime ExpiryDate { get; set; }  // UTC
    public bool IsActive { get; set; }        // StartDate ≤ now < ExpiryDate
}

3. Video Catalog

Video

Represents a movie or show.

public class Video
{
    public int Id { get; set; }
    public string Title { get; set; }
    public string Description { get; set; }
    public string Url { get; set; }           // Streaming URL
    public string ThumbnailUrl { get; set; }
    public string Category { get; set; }      // e.g. “Sci-Fi”
    public double Rating { get; set; }        // Average user rating
    public bool IsDeleted { get; set; }       // Soft delete flag
    public DateTime CreatedAt { get; set; }   // UTC
}

4. Favorites

Favorite

Join entity between Profile and Video.

public class Favorite
{
    public int ProfileId { get; set; }
    public Profile Profile { get; set; }

    public int VideoId { get; set; }
    public Video Video { get; set; }

    public DateTime AddedAt { get; set; }     // UTC
}

5. Payments

PaymentTransaction

Tracks Paymob/payment provider interactions.

public class PaymentTransaction
{
    public int Id { get; set; }
    public string OrderId { get; set; }           // Internal order reference
    public string TransactionId { get; set; }     // Provider’s transaction token

    public int AmountCents { get; set; }
    public string Currency { get; set; }          // e.g. “EGP”

    public string Status { get; set; }            // e.g. “PENDING”, “CAPTURED”
    public DateTime CreatedAt { get; set; }       // UTC
    public DateTime? PaidAt { get; set; }         // UTC when captured
}

6. AI-Powered Recommendations

MovieRecommendation

Encapsulates a single recommendation result.

public class MovieRecommendation
{
    public int Id { get; set; }                   // Video Id
    public string Title { get; set; }
    public string Description { get; set; }
    public string ImageUrl { get; set; }
    public double Rating { get; set; }
    public string Category { get; set; }
    public string Reason { get; set; }            // Why it’s recommended
}

Relationships Overview

  • One ApplicationUser → Many Profile
  • One Profile ↔ Many Favorite ↔ One Video
  • One ApplicationUser → Many UserSubscription → One SubscriptionPlan
  • PaymentTransaction logs each purchase tied to an OrderId

Understanding these models and associations enables you to implement authentication flows, manage subscriptions, curate content, and integrate payments and recommendations seamlessly.

CategoryController: CRUD Operations

Purpose
Manage video categories: list, retrieve, create, update, and delete.


1. List Categories (GET /api/categories)

Supports pagination via pageNumber and pageSize query parameters.

Query Parameters
• pageNumber (int, default 1)
• pageSize (int, default 10)

Request
GET /api/categories?pageNumber=1&pageSize=10

Success Response

HTTP 200 OK
{
  "data": [
    { "id": 1, "name": "Action", "description": "Fast-paced movies" },
    { "id": 2, "name": "Drama",  "description": "Story-driven films" }
  ],
  "pagination": {
    "pageNumber": 1,
    "pageSize":   10,
    "totalCount": 2,
    "totalPages": 1
  }
}

2. Get Category by ID (GET /api/categories/{id})

Path Parameter
• id (int)

Success Response

HTTP 200 OK
{
  "id":          1,
  "name":        "Action",
  "description": "Fast-paced movies"
}

Error Response (not found)

HTTP 404 Not Found
{ "message": "Category not found." }

3. Create Category (POST /api/categories)

Request Payload

{
  "name":        "Sci-Fi",
  "description": "Science fiction films"
}

Success Response

HTTP 201 Created
Location: /api/categories/3

{
  "id":          3,
  "name":        "Sci-Fi",
  "description": "Science fiction films"
}

Error Response (validation)

HTTP 400 Bad Request
{ "message": "Name is required." }

4. Update Category (PUT /api/categories/{id})

Path Parameter
• id (int)

Request Payload

{
  "name":        "Science Fiction",
  "description": "Sci-Fi movies and series"
}

Success Response

HTTP 204 No Content

Error Response (not found)

HTTP 404 Not Found
{ "message": "Category not found." }

5. Delete Category (DELETE /api/categories/{id})

Path Parameter
• id (int)

Success Response

HTTP 204 No Content

Error Response (not found)

HTTP 404 Not Found
{ "message": "Category not found." }

C# HttpClient Usage Examples

Necessary Imports

using System.Net.Http;
using System.Net.Http.Json;
using Netflix.API.DTOs.CategoryDtos;
// Fetch paged list of categories
async Task<PagedResponse<CategoryDto>> GetCategoriesAsync(HttpClient client, int pageNumber = 1, int pageSize = 10)
{
    var url = $"/api/categories?pageNumber={pageNumber}&pageSize={pageSize}";
    return await client.GetFromJsonAsync<PagedResponse<CategoryDto>>(url);
}

// Retrieve a single category by ID
async Task<CategoryDto> GetCategoryByIdAsync(HttpClient client, int id)
{
    var response = await client.GetAsync($"/api/categories/{id}");
    response.EnsureSuccessStatusCode();
    return await response.Content.ReadFromJsonAsync<CategoryDto>();
}

// Create a new category
async Task<CategoryDto> CreateCategoryAsync(HttpClient client, CreateCategoryDto dto)
{
    var response = await client.PostAsJsonAsync("/api/categories", dto);
    response.EnsureSuccessStatusCode();
    return await response.Content.ReadFromJsonAsync<CategoryDto>();
}

// Update an existing category
async Task UpdateCategoryAsync(HttpClient client, int id, UpdateCategoryDto dto)
{
    var response = await client.PutAsJsonAsync($"/api/categories/{id}", dto);
    response.EnsureSuccessStatusCode();
}

// Delete a category
async Task DeleteCategoryAsync(HttpClient client, int id)
{
    var response = await client.DeleteAsync($"/api/categories/{id}");
    response.EnsureSuccessStatusCode();
}

Practical Tips
• Validate CreateCategoryDto and UpdateCategoryDto on the client to surface errors.
• Use totalCount from pagination metadata to drive UI paging controls.
• Secure CategoryController with [Authorize(Roles = "Admin")].
• Handle HTTP 404 to inform users when a category doesn’t exist.

Routing and Guards

This section explains how the Angular app defines its routes, applies lazy loading, and secures pages using AuthGuard.

Overview

  • Routes split into public, profile, and admin sections.
  • Admin area uses nested child routes and lazy-loaded components.
  • AuthGuard protects profile and admin routes, enforcing authentication and admin roles.

Route Configuration (app.routes.ts)

// Netflix/src/app/app.routes.ts
import { Routes } from '@angular/router';
import { AuthGuard } from './core/guards/auth.guard';

export const appRoutes: Routes = [
  // Public pages
  { path: 'login',  loadComponent: () => import('./features/auth/login.component')
                                       .then(m => m.LoginComponent) },
  { path: 'register', loadComponent: () => import('./features/auth/register.component')
                                          .then(m => m.RegisterComponent) },

  // User profile (requires authentication)
  {
    path: 'profile',
    loadComponent: () => import('./features/profile/profile.component')
                         .then(m => m.ProfileComponent),
    canActivate: [AuthGuard]
  },

  // Admin area (nested routes, requires admin)
  {
    path: 'admin',
    loadComponent: () => import('./features/admin/admin.component')
                         .then(m => m.AdminComponent),
    canActivate: [AuthGuard],
    children: [
      { path: '', redirectTo: 'dashboard', pathMatch: 'full' },
      {
        path: 'dashboard',
        loadComponent: () => import('./features/admin/dashboard.component')
                             .then(m => m.AdminDashboardComponent)
      },
      {
        path: 'movies',
        loadComponent: () => import('./features/admin/movies/movies-statistics.component')
                             .then(m => m.MoviesStatisticsComponent),
        children: [
          { path: '', redirectTo: 'all', pathMatch: 'full' },
          { path: 'all',       loadComponent: () => import('./features/admin/movies/admin-movies.component').then(m => m.AdminMoviesComponent) },
          { path: 'published', loadComponent: () => import('./features/admin/movies/published-movies.component').then(m => m.PublishedMoviesComponent) },
          { path: 'archived',  loadComponent: () => import('./features/admin/movies/deleted-movies.component').then(m => m.DeletedMoviesComponent) },
          { path: 'add',       loadComponent: () => import('./features/admin/movies/add-movie.component').then(m => m.AddMovieComponent) },
        ]
      },
      { path: 'moviedetails/:id', loadComponent: () => import('./features/admin/movies/admin-movie-details.component').then(m => m.AdminMovieDetailsComponent) }
    ]
  },

  // Fallback
  { path: '**', redirectTo: 'login' }
];

AuthGuard Behavior

  • Checks authService.isAuthenticated().
  • For any route containing admin, verifies authService.getCurrentUser().isAdmin.
  • Redirects unauthenticated users to /login.
  • Redirects non-admins from /admin/* to /profile.

Guard Registration

// Netflix/src/app/core/guards/auth.guard.ts
@Injectable({ providedIn: 'root' })
export class AuthGuard implements CanActivate {
  constructor(private auth: AuthService, private router: Router) {}

  canActivate(route: ActivatedRouteSnapshot): boolean {
    if (!this.auth.isAuthenticated()) {
      this.router.navigate(['login']);
      return false;
    }
    const isAdminRoute = route.url.some(seg => seg.path === 'admin');
    if (isAdminRoute && !this.auth.getCurrentUser()?.isAdmin) {
      this.router.navigate(['profile']);
      return false;
    }
    return true;
  }
}

Lazy Loading Best Practices

  • Use loadComponent for standalone components or loadChildren for NgModules.
  • Keep initial bundle small by deferring admin features until needed.

Navigation Examples

Template Links

<!-- Anywhere in app -->
<a routerLink="/profile">My Profile</a>
<a routerLink="/admin/movies/all">All Movies</a>

Programmatic Navigation

import { Router } from '@angular/router';

@Component({ /* ... */ })
export class SomeComponent {
  constructor(private router: Router) {}

  goToDashboard() {
    this.router.navigate(['admin', 'dashboard']);
  }

  viewMovie(id: string) {
    this.router.navigate(['admin', 'moviedetails', id]);
  }
}

Practical Tips

  • Always place a <router-outlet> in AdminComponent and MoviesStatisticsComponent templates.
  • Use redirectTo on empty paths to set sensible defaults.
  • Protect top-level routes with canActivate: [AuthGuard] rather than per child.
  • Verify your component paths in Netflix/src/app/features/... match the route imports exactly.

Implementing Global Exception Handling Middleware

Purpose: Centralize unhandled exception capture and return consistent JSON error responses across the API.

1. ErrorHandlingMiddleware Class

Place this in Middleware/ErrorHandlingMiddleware.cs. It intercepts exceptions, logs them, and formats a ProblemDetails response.

using System.Net;
using System.Text.Json;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;

namespace Netflix.API.Middleware
{
    public class ErrorHandlingMiddleware
    {
        private readonly RequestDelegate _next;
        private readonly ILogger<ErrorHandlingMiddleware> _logger;

        public ErrorHandlingMiddleware(RequestDelegate next, ILogger<ErrorHandlingMiddleware> logger)
        {
            _next = next;
            _logger = logger;
        }

        public async Task InvokeAsync(HttpContext context)
        {
            try
            {
                await _next(context);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Unhandled exception for {Path}", context.Request.Path);
                await HandleExceptionAsync(context, ex);
            }
        }

        private static Task HandleExceptionAsync(HttpContext context, Exception exception)
        {
            var details = new
            {
                type    = "https://tools.ietf.org/html/rfc7231#section-6.6.1",
                title   = "An unexpected error occurred.",
                status  = (int)HttpStatusCode.InternalServerError,
                detail  = exception.Message
            };

            var payload = JsonSerializer.Serialize(details);
            context.Response.ContentType = "application/problem+json";
            context.Response.StatusCode  = details.status;
            return context.Response.WriteAsync(payload);
        }
    }
}

2. Registration in Program.cs

In Program.cs, inject the middleware at the top of the pipeline before MVC:

var builder = WebApplication.CreateBuilder(args);

// … other service registrations

var app = builder.Build();

// Global exception handler
app.UseMiddleware<Netflix.API.Middleware.ErrorHandlingMiddleware>();

app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();

app.MapControllers();

app.Run();

3. Customizing Responses

  • Extend details object to include a unique traceId:
context.Response.Headers.Add("X-Trace-Id", context.TraceIdentifier);
  • Map specific exception types to different HTTP statuses:
private static Task HandleExceptionAsync(HttpContext context, Exception exception)
{
    var (status, title) = exception switch
    {
        UnauthorizedAccessException _ => ((int)HttpStatusCode.Unauthorized, "Unauthorized"),
        KeyNotFoundException _       => ((int)HttpStatusCode.NotFound, "Resource Not Found"),
        _                            => (500, "An unexpected error occurred.")
    };
    var details = new { type = $"https://httpstatuses.com/{status}", title, status, detail = exception.Message };
    …
}

4. Practical Tips

• Log at the most detailed level you need (e.g., include stack traces in Development only).
• Return minimal error details in Production to avoid leaking internal info.
• Write unit tests that simulate exceptions in controller actions to verify middleware behavior.
• Combine with UseHttpsRedirection() and UseCors() before your exception handler to catch all pipeline errors.

Development & Contribution Guide

This guide covers setting up your local environment, running tests, enforcing code style, and deploying the Netflix Clone app.

Prerequisites

  • Node.js v16+ and npm
  • .NET SDK 7.0+ (backend)
  • Angular CLI v15+
  • Git

Repository Setup

  1. Clone the repo
    git clone https://github.com/Moaz-93/Netflix_Clone.git
    cd Netflix_Clone
    
  2. Install frontend dependencies
    cd client
    npm install
    
  3. Install backend dependencies
    cd ../server
    dotnet restore
    

Local Development

Backend

  1. Configure appsettings.Development.json with your DB/credentials.
  2. Run the API
    cd server
    dotnet run --project NetflixClone.Api
    
  3. The API listens on https://localhost:7140 by default.

Frontend

  1. Use Angular’s proxy to avoid CORS. In client/proxy.conf.json:
    {
      "/api": {
        "target": "https://localhost:7140",
        "secure": false,
        "changeOrigin": true
      }
    }
    
  2. Start dev server
    cd client
    ng serve --proxy-config proxy.conf.json
    
  3. Navigate to http://localhost:4200.

Testing

Frontend Unit Tests

cd client
npm run test
  • Runs Karma + Jasmine.
  • Watch coverage in client/coverage/index.html.

Backend Unit Tests

cd server/tests/NetflixClone.Tests
dotnet test --logger:"console;verbosity=detailed"

Code Style & Linting

Frontend

  • ESLint + Prettier enforce TypeScript style.
  • Run lint
    cd client
    npm run lint
    
  • Auto-fix issues
    npm run lint:fix
    

Backend

  • Use EditorConfig and .NET analyzers in server/NetflixClone.Api.csproj.
  • Format via
    dotnet format
    

Commit Messages

Follow Conventional Commits:

feat(login): add OAuth flow
fix(api): handle null user id
chore(deps): update Newtonsoft.Json to v13

Production Deployment

Frontend Build

  1. Update client/src/environments/environment.prod.ts with your live API URL:
    export const environment = {
      production: true,
      apiUrl: 'https://api.my-netflix.com/v1'
    };
    
  2. Build for prod
    cd client
    ng build --configuration=production
    
  3. Serve client/dist/client via any static host (NGINX, S3, etc.).

Backend Publish

cd server
dotnet publish NetflixClone.Api -c Release -o ../deploy/api
  • Deploy the deploy/api folder to your server or container.

End-to-End

  1. Serve frontend static files with a reverse proxy to /api.
  2. Ensure HTTPS certificates are configured.
  3. Monitor logs and health endpoints (/health) in production.

Contribution Workflow

  1. Fork and branch from main
    git checkout -b feat/your-feature
    
  2. Implement code; run all tests and linting.
  3. Push and open a Pull Request targeting main.
  4. Linking issues and following the PR template speeds review.

Please adhere to this guide to ensure smooth development, testing, and deployment of the Netflix Clone application.