Skip to content

nghuuquyen/251DCSECSB35050001-GROUP4

 
 

Repository files navigation

📝 MY TODO – Task Management System


👤 Student Information

  • Full Name: Phan Thị Thùy Nhung
  • Student ID: 23020006

📖 Project Overview

MY TODO is a web-based task management system developed as a course project.
The application helps users organize their tasks efficiently by categories, priorities, and due dates.

The system provides a simple and user-friendly interface while maintaining essential task management features commonly used in real-world applications.

Key Features

  • User authentication (login system – planned for future extension)
  • Create, update, and delete tasks
  • Categorize tasks (system and custom categories)
  • Set task priorities and due dates
  • Filter tasks by category and priority
  • Warning section for overdue and due-today tasks

🖼️ Demo Screenshots

Home / Task Board

Authentication

Task Management

Validation Example


🛠️ Technologies Used

Frontend

  • HTML5
  • CSS3
  • Vanilla JavaScript

Backend

  • PHP (Laravel Framework)

Database

  • MySQL

Other Tools

  • Git & GitHub
  • Composer

⚙️ Installation & Setup

Prerequisites

  • PHP (>= 8.0)
  • Composer
  • MySQL

Clone & Install

git clone https://github.com/pttnhung/my-todo.git
cd my-todo
composer install

Environment Setup

cp .env.example .env
php artisan key:generate
php artisan migrate

Run the Application

php artisan serve

Access the application at:

http://127.0.0.1:8000

🚀 Performance Optimizations (Caching & Anti-pattern Mitigation)

This project implements several optimizations to reduce database load and improve response time.


1) Server-side Caching for API Responses (Database Cache Store)

Cache store configuration (.env):

CACHE_STORE=database

Create cache table (only once):

php artisan cache:table
php artisan migrate

Why caching?

  • GET /api/tasks and GET /api/categories are frequently requested by the frontend.
  • Without caching, the app would query the database repeatedly for the same data.
  • With caching, the app returns cached results until they expire or data changes.

2) Cache Invalidation Strategy (Version-based Invalidation)

Because CACHE_STORE=database does not support cache tags, the project uses a version-based invalidation approach:

  • Each user has a cache version key:

    • tasks:v:{userId}
    • categories:v:{userId}
  • Cached list keys include the current version:

    • tasks:user:{userId}:v:{version}:qs:{hash}
    • categories:user:{userId}:v:{version}
  • When data changes (create/update/delete), the version increments → old cached lists become unused automatically.

✅ Code (CategoryController – cache + invalidation)

use Illuminate\Support\Facades\Cache;
use Illuminate\Validation\Rule;

private function catVersionKey(int $userId): string
{
    return "categories:v:$userId";
}

private function bumpCatVersion(int $userId): void
{
    Cache::increment($this->catVersionKey($userId));
}

public function index(Request $request)
{
    $userId = $request->user()->id;

    $v = Cache::get($this->catVersionKey($userId), 1);
    $cacheKey = "categories:user:$userId:v:$v";

    $data = Cache::remember($cacheKey, now()->addMinutes(10), function () use ($userId) {
        return Category::query()
            ->where(function ($q) use ($userId) {
                $q->where('user_id', $userId)
                  ->orWhere(function ($q2) {
                      $q2->where('user_id', 1)->where('is_system', 1);
                  });
            })
            ->orderBy('is_system', 'desc')
            ->orderBy('sort_order')
            ->orderBy('created_at')
            ->get();
    });

    return response()
        ->json($data)
        ->header('Cache-Control', 'private, max-age=600');
}

// After create/update/delete:
$this->bumpCatVersion($userId);

✅ Code (TaskController – cache + invalidation)

use Illuminate\Support\Facades\Cache;

private function taskVersionKey(int $userId): string
{
    return "tasks:v:$userId";
}

private function bumpTaskVersion(int $userId): void
{
    Cache::increment($this->taskVersionKey($userId));
}

public function index(Request $request)
{
    $userId = $request->user()->id;

    $qs = http_build_query($request->query());
    $qsHash = md5($qs);

    $v = Cache::get($this->taskVersionKey($userId), 1);
    $cacheKey = "tasks:user:$userId:v:$v:qs:$qsHash";

    $data = Cache::remember($cacheKey, now()->addSeconds(60), function () use ($userId) {
        return Task::query()
            ->with('category') // prevents N+1 queries
            ->where('user_id', $userId)
            ->orderBy('due')
            ->orderBy('created_at', 'desc')
            ->get();
    });

    return response()
        ->json($data)
        ->header('Cache-Control', 'private, max-age=60');
}

// After create/update/delete:
$this->bumpTaskVersion($userId);

3) Fix Common Performance Anti-patterns

A) Preventing N+1 Queries (Eager Loading)

To avoid querying category once per task (N+1), tasks are loaded with related category using:

Task::query()->with('category')->where('user_id', $userId)->get();

This reduces unnecessary database queries and improves scalability.

B) Fixing Validation Bug + Enforcing Unique Category Names Per User

Each user cannot create duplicate category names. The project enforces this rule via validation:

$data = $request->validate([
    'name' => [
        'required', 'string', 'max:100',
        Rule::unique('categories', 'name')->where(fn ($q) => $q->where('user_id', $userId)),
    ],
]);

4) Database Query Optimization with Indexes (Migration)

To reduce database bottlenecks and speed up common filters/sorting, the project adds composite indexes on frequently queried columns.

Migration example:

Schema::table('tasks', function (Blueprint $table) {
    $table->index(['user_id', 'created_at']);
    $table->index(['user_id', 'due']);
    $table->index(['user_id', 'status']);
    $table->index(['user_id', 'category_id']);
});

Schema::table('categories', function (Blueprint $table) {
    $table->index(['user_id', 'sort_order']);
    $table->index(['user_id', 'name']);
    $table->index(['user_id', 'is_system']);
});

Verify indexes in MySQL:

SHOW INDEX FROM tasks;
SHOW INDEX FROM categories;

📌 Notes

  • Default system categories are predefined and cannot be deleted.
  • User authentication (login/register) is included in the project scope but may not be fully implemented yet.
  • All API endpoints are served under /api/*.
  • This project is developed for educational purposes as part of a university course.

📄 License

This project is created for academic use only.


About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages

  • PHP 38.6%
  • Blade 27.3%
  • HTML 15.7%
  • JavaScript 10.3%
  • CSS 8.1%