পাঠ ৫৮ · ৫৮-এর মধ্যে · মডিউল ১৩
Home / Courses / Full-Stack Web Frameworks / ক্যাপস্টোন প্রকল্প

চূড়ান্ত প্রকল্প — একটি ফুল-স্ট্যাক অ্যাপ্লিকেশন ডিজাইন ও সিমুলেট করা

Capstone — designing and simulating a full-stack application
১৬ মিনিট পড়া উন্নত · Advanced Python কোডসহ সম্পূর্ণ বাংলায়

এই পাঠে যা শিখবেন

  • কোর্সের একাধিক সিমুলেশন প্যাটার্নকে (Router, CRUD DB, ORM, JWT, Redux store) একটি সুসংগত সিস্টেমে সংযুক্ত করা
  • একটি real HMAC-SHA256 JWT দিয়ে অথেন্টিকেশন মিডলওয়্যার তৈরি করা যা একটি প্রোটেক্টেড রুট রক্ষা করে
  • টোকেন ছাড়া রিকোয়েস্ট কীভাবে সঠিকভাবে প্রত্যাখ্যাত হয় (401), আর সঠিক টোকেনসহ রিকোয়েস্ট কীভাবে গ্রহণযোগ্য হয় (201) — উভয়ই একই মিডলওয়্যার দিয়ে, একই কোডে
  • একটি সম্পূর্ণ এন্ড-টু-এন্ড ব্যবহারকারী ফ্লো ট্রেস করা — রেজিস্ট্রেশন থেকে চূড়ান্ত ক্লায়েন্ট-সাইড স্টেট পর্যন্ত

১ · ক্যাপস্টোন পরিকল্পনা — একটি টাস্ক ম্যানেজার সিমুলেট করা

কোর্স জুড়ে আমরা একটি একটি করে ফ্রেমওয়ার্ক-স্তরের ধারণা শিখেছি — Router (M5), CRUD ডেটাবেস (M6), ORM (M7), JWT-ভিত্তিক অথেন্টিকেশন (M8), ও Redux-স্টাইল স্টেট ম্যানেজমেন্ট (M4)। এই ক্যাপস্টোনে সেগুলো আলাদা আলাদা না রেখে একসাথে জোড়া লাগিয়ে একটি ছোট্ট কিন্তু সম্পূর্ণ, কার্যকর "টাস্ক ম্যানেজার" ফুল-স্ট্যাক অ্যাপ্লিকেশন সিমুলেট করা হবে।

ক্লায়েন্ট Redux-স্টাইল স্টোর Router + Auth মিডলওয়্যার verify_jwt() যাচাই করে তবেই হ্যান্ডলার কল করে ORM Model Task.objects CRUD DB TASKS_DB টোকেন ছাড়া রিকোয়েস্ট মিডলওয়্যারেই আটকে যায় -- কখনো ORM/DB পর্যন্ত পৌঁছায় না
পাঁচটি প্যাটার্ন একসাথে — Router, CRUD ডেটাবেস, ORM, JWT অথ মিডলওয়্যার, ও ক্লায়েন্ট-সাইড Redux স্টোর — একটি সম্পূর্ণ টাস্ক-ম্যানেজার ফ্লোতে যুক্ত।
নিচের কোড সেলগুলো একসাথে একটি একক Pyodide সেশনে চলে — একটি সেলে সংজ্ঞায়িত ক্লাস/ফাংশন পরের সেলে ব্যবহার করা যায়, ঠিক যেমন একটি Jupyter নোটবুকে হয়। তাই সঠিক ফলাফল পেতে সেলগুলো উপর থেকে নিচে, ক্রমানুসারে Run করুন।

২ · মডিউল ১ — Task CRUD ডেটাবেস

প্রথম টুকরোটি হলো একটি ইন-মেমরি CRUD "ডেটাবেস" — একটি Python ডিকশনারি (TASKS_DB) যেখানে প্রতিটি টাস্ক একটি অটো-ইনক্রিমেন্টিং আইডি দিয়ে সংরক্ষিত হয়, সাথে সত্যিকারের create/read/ update/delete ফাংশন (M6-M7-এ শেখা CRUD প্যাটার্ন)।

Python
# ========== মডিউল ১: In-memory CRUD "ডেটাবেস" -- টাস্কের জন্য ==========
TASKS_DB = {}        # id (int) থেকে রেকর্ড dict -- সত্যিকারের ডেটাবেস টেবিলের ভূমিকায়
_next_task_id = 1

def db_create_task(title, owner):
    global _next_task_id
    record = {"id": _next_task_id, "title": title, "done": False, "owner": owner}
    TASKS_DB[_next_task_id] = record
    _next_task_id += 1
    return dict(record)

def db_read_task(task_id):
    record = TASKS_DB.get(task_id)
    return dict(record) if record else None

def db_list_tasks():
    return [dict(r) for r in TASKS_DB.values()]

def db_update_task(task_id, **fields):
    record = TASKS_DB.get(task_id)
    if record is None:
        return None
    record.update(fields)
    return dict(record)

def db_delete_task(task_id):
    return TASKS_DB.pop(task_id, None) is not None


# ---- স্যানিটি-চেক: CRUD ফাংশনগুলো সরাসরি টেস্ট করা ----
demo = db_create_task("ডেমো টাস্ক (শুধু CRUD যাচাইয়ের জন্য)", owner="system")
print("তৈরি:      ", demo)
print("পড়া:       ", db_read_task(demo["id"]))
print("আপডেট:     ", db_update_task(demo["id"], done=True))
print("তালিকা:    ", db_list_tasks())
print("মোছা হলো:  ", db_delete_task(demo["id"]))
print("মোছার পর:  ", db_list_tasks())

    

৩ · মডিউল ২ — মিনি ORM Model

দ্বিতীয় টুকরোটি হলো একটি মিনি ORM (M7-এর ORM প্যাটার্ন) — একটি Task ক্লাস যা উপরের CRUD ফাংশনগুলো ভেতরে ভেতরে কল করে, কিন্তু বাইরে থেকে একটি অনেক পরিষ্কার অবজেক্ট ইন্টারফেস দেয় (Task.create(...), task.save(), Task.objects.filter(...))।

Python
# ========== মডিউল ২: মিনি ORM -- Task মডেল, উপরের CRUD ডেটাবেস র‍্যাপ করে ==========
class Manager:
    def __init__(self, model_cls, list_fn):
        self.model_cls = model_cls
        self.list_fn = list_fn

    def filter(self, **kwargs):
        rows = [r for r in self.list_fn() if all(r.get(k) == v for k, v in kwargs.items())]
        return [self.model_cls(**r) for r in rows]

    def all(self):
        return self.filter()


class Task:
    def __init__(self, id, title, done, owner):
        self.id = id
        self.title = title
        self.done = done
        self.owner = owner

    @classmethod
    def create(cls, title, owner):
        record = db_create_task(title, owner)
        return cls(**record)

    def save(self):
        db_update_task(self.id, title=self.title, done=self.done, owner=self.owner)
        return self

    def __repr__(self):
        mark = "[x]" if self.done else "[ ]"
        return f"{mark} #{self.id} {self.title} (owner={self.owner})"


Task.objects = Manager(Task, db_list_tasks)

# ---- মিনি-ORM যাচাই: তৈরি, ফিল্টার, আপডেট, আবার পড়া ----
task_a = Task.create(title="ডিজাইন রিভিউ", owner="rafiq")
task_b = Task.create(title="ডেপ্লয়মেন্ট চেকলিস্ট", owner="rafiq")
task_c = Task.create(title="মিটিং নোট", owner="admin")

print("সব টাস্ক:", Task.objects.all())
print("\nrafiq-এর টাস্কসমূহ:", Task.objects.filter(owner="rafiq"))

task_a.done = True
task_a.save()
print("\ntask_a সেভ করার পর, id দিয়ে আবার পড়া হলো:", Task.objects.filter(id=task_a.id))

    
লক্ষ্য করুন Task ক্লাসের ভেতরে কোথাও সরাসরি TASKS_DB স্পর্শ করা হয়নি — সবসময় db_create_task/db_update_task/db_list_tasks ফাংশনের মাধ্যমে যায়। এটাই ORM-এর মূল সুবিধা: ভবিষ্যতে TASKS_DB-এর ভেতরের স্টোরেজ বদলে ফেললেও (যেমন একটি আসল ডেটাবেস টেবিলে) Task ক্লাসের ব্যবহারকারী কোডের কিছু বদলাতে হয় না।

৪ · মডিউল ৩ — JWT-ভিত্তিক অথেন্টিকেশন

তৃতীয় টুকরোটি হলো real JWT — M8/L34-এ শেখা একই পদ্ধতি পুনর্ব্যবহার করে: hashlib, hmac, base64, ও json স্ট্যান্ডার্ড লাইব্রেরি দিয়ে একটি সত্যিকারের HMAC-SHA256-সাইন করা টোকেন তৈরি ও যাচাই করা হয় — কোনো প্লেসহোল্ডার স্ট্রিং নয়।

Python
# ========== মডিউল ৩: JWT-ভিত্তিক অথেন্টিকেশন (real HMAC-SHA256, M8/L34-এর পদ্ধতি পুনর্ব্যবহার) ==========
import hashlib, hmac, base64, json, time

JWT_SECRET = "capstone-super-secret-key"  # বাস্তবে env var/secrets manager-এ রাখা উচিত (L51)


def _b64url_encode(data):
    return base64.urlsafe_b64encode(data).rstrip(b"=").decode("ascii")


def _b64url_decode(s):
    padding = "=" * (-len(s) % 4)
    return base64.urlsafe_b64decode(s + padding)


def create_jwt(payload, secret=JWT_SECRET):
    header = {"alg": "HS256", "typ": "JWT"}
    header_b64 = _b64url_encode(json.dumps(header, separators=(",", ":")).encode())
    payload_b64 = _b64url_encode(json.dumps(payload, separators=(",", ":")).encode())
    signing_input = f"{header_b64}.{payload_b64}".encode()
    signature = hmac.new(secret.encode(), signing_input, hashlib.sha256).digest()
    signature_b64 = _b64url_encode(signature)
    return f"{header_b64}.{payload_b64}.{signature_b64}"


def verify_jwt(token, secret=JWT_SECRET):
    try:
        header_b64, payload_b64, signature_b64 = token.split(".")
    except ValueError:
        return None
    signing_input = f"{header_b64}.{payload_b64}".encode()
    expected_sig = hmac.new(secret.encode(), signing_input, hashlib.sha256).digest()
    expected_sig_b64 = _b64url_encode(expected_sig)
    if not hmac.compare_digest(expected_sig_b64, signature_b64):
        return None
    return json.loads(_b64url_decode(payload_b64))


# ---- JWT তৈরি ও সঠিক যাচাই ----
sample_token = create_jwt({"sub": "test_user", "iat": int(time.time())})
print("তৈরি হওয়া টোকেন:", sample_token)

verified_payload = verify_jwt(sample_token)
print("সঠিক টোকেন যাচাইয়ের ফলাফল:", verified_payload)
assert verified_payload is not None and verified_payload["sub"] == "test_user"

# ---- টেম্পারিং সিমুলেট করা: payload অংশের শেষ অক্ষর বদলে দেওয়া ----
header_b64, payload_b64, signature_b64 = sample_token.split(".")
tampered_last_char = "A" if payload_b64[-1] != "A" else "B"
tampered_payload_b64 = payload_b64[:-1] + tampered_last_char
tampered_token = f"{header_b64}.{tampered_payload_b64}.{signature_b64}"

tampered_result = verify_jwt(tampered_token)
print("\nটেম্পার করা টোকেন যাচাইয়ের ফলাফল:", tampered_result)
assert tampered_result is None, "টেম্পার করা টোকেন যাচাই ব্যর্থ হওয়া উচিত ছিল"
print("টেম্পার-শনাক্তকরণ কাজ করছে: স্বাক্ষর মিলছে না, তাই verify_jwt() None রিটার্ন করেছে।")

    
verify_jwt() কখনোই payload_b64 ডিকোড করার আগে স্বাক্ষর মিলিয়ে দেখে — hmac.compare_digest() দিয়ে কনস্ট্যান্ট-টাইম তুলনা করা হয় (টাইমিং অ্যাটাক ঠেকাতে)। টেম্পার করা টোকেনে সিগনেচার অপরিবর্তিত থাকলেও পরিবর্তিত payload_b64 দিয়ে পুনরায় গণনা করা প্রত্যাশিত সিগনেচার আর মূল সিগনেচারের সাথে মেলে না — তাই যাচাই ব্যর্থ হয়, None রিটার্ন হয়।

৫ · মডিউল ৪ — Router, Auth মিডলওয়্যার ও রুট ওয়্যারিং

চতুর্থ টুকরোটি সব একসাথে জোড়া লাগায় — একটি Router (M5), একটি auth_required মিডলওয়্যার (M5/M8) যা মডিউল ৩-এর verify_jwt() ব্যবহার করে, এবং চারটি রুট: রেজিস্ট্রেশন, লগইন, টাস্ক তালিকা (পাবলিক), ও টাস্ক তৈরি (প্রোটেক্টেড)। শুধু টাস্ক তৈরি রুটটিই টোকেন দাবি করে — এই সরলীকৃত টুলকিটে তালিকা দেখা একটি পাবলিক টাস্ক-বোর্ডের মতো উন্মুক্ত রাখা হয়েছে।

Python
# ========== মডিউল ৪: ইউজার স্টোর, Router ও Auth মিডলওয়্যার ==========
import hashlib, time

USERS_DB = {}  # username থেকে {"username":.., "password_hash":..}

def hash_password(password):
    # সরলতার জন্য salt ছাড়া SHA-256 -- বাস্তব প্রোডাকশনে bcrypt/argon2/PBKDF2 ব্যবহার করা উচিত
    return hashlib.sha256(password.encode()).hexdigest()


class Router:
    def __init__(self):
        self.routes = {}

    def route(self, method, path):
        def decorator(handler_fn):
            self.routes[(method, path)] = handler_fn
            return handler_fn
        return decorator

    def dispatch(self, method, path, request=None):
        request = request or {}
        handler = self.routes.get((method, path))
        if handler is None:
            return {"status": 404, "body": {"error": "Not Found"}}
        return handler(request)


app = Router()


def auth_required(handler_fn):
    """মিডলওয়্যার -- Authorization হেডার থেকে JWT বের করে verify_jwt() দিয়ে যাচাই করে,
    তবেই আসল হ্যান্ডলার কল করে -- অন্যথায় হ্যান্ডলার পর্যন্ত পৌঁছানোর আগেই 401 রিটার্ন করে"""
    def wrapped(request):
        auth_header = request.get("headers", {}).get("Authorization", "")
        if not auth_header.startswith("Bearer "):
            return {"status": 401, "body": {"error": "Unauthorized -- টোকেন পাওয়া যায়নি"}}
        raw_token = auth_header[len("Bearer "):]
        payload = verify_jwt(raw_token)
        if payload is None:
            return {"status": 401, "body": {"error": "Unauthorized -- টোকেন অবৈধ বা টেম্পার করা"}}
        request["user"] = payload["sub"]
        return handler_fn(request)
    return wrapped


@app.route("POST", "/register")
def register(request):
    username = request["body"]["username"]
    password = request["body"]["password"]
    if username in USERS_DB:
        return {"status": 409, "body": {"error": "এই ইউজারনেম আগে থেকেই আছে"}}
    USERS_DB[username] = {"username": username, "password_hash": hash_password(password)}
    return {"status": 201, "body": {"username": username}}


@app.route("POST", "/login")
def login(request):
    username = request["body"]["username"]
    password = request["body"]["password"]
    user = USERS_DB.get(username)
    if user is None or user["password_hash"] != hash_password(password):
        return {"status": 401, "body": {"error": "ইউজারনেম বা পাসওয়ার্ড ভুল"}}
    token = create_jwt({"sub": username, "iat": int(time.time())})
    return {"status": 200, "body": {"token": token}}


@app.route("GET", "/tasks")
def list_tasks_route(request):
    tasks = [{"id": t.id, "title": t.title, "done": t.done, "owner": t.owner} for t in Task.objects.all()]
    return {"status": 200, "body": {"tasks": tasks}}


@app.route("POST", "/tasks")
@auth_required
def create_task_route(request):
    title = request["body"]["title"]
    task = Task.create(title=title, owner=request["user"])
    return {"status": 201, "body": {"task": {"id": task.id, "title": task.title, "done": task.done, "owner": task.owner}}}


print("রেজিস্টার করা রুটসমূহ:")
for method, path in app.routes:
    print(f"  {method:5s} {path}")

    
লক্ষ্য করুন @app.route("POST", "/tasks")-এর নিচে @auth_required — ডেকোরেটর নিচ থেকে উপরে প্রয়োগ হয়, তাই আসলে যা রেজিস্টার হয় তা হলো auth_required(create_task_route)-এর রিটার্ন করা wrapped ফাংশন। মানে Router.dispatch() যখন এই রুটে পৌঁছায়, প্রথমে টোকেন যাচাই হয় — যাচাই ব্যর্থ হলে আসল create_task_route লজিকটি একবারও কল হয় না।

৬ · মডিউল ৫ — ক্লায়েন্ট-সাইড Redux-স্টাইল স্টেট স্টোর

পঞ্চম টুকরোটি ক্লায়েন্ট-পাশে — M4-এ শেখা Redux/Flux প্যাটার্নের একটি ছোট্ট, সত্যিকারের কার্যকর সংস্করণ। সার্ভার থেকে যে রেসপন্স আসবে (মডিউল ৪-এর রুট থেকে), ক্লায়েন্ট সেটাকে একটি অ্যাকশন হিসেবে এই স্টোরে dispatch করবে, আর reducer একটি নতুন স্টেট অবজেক্ট তৈরি করবে (immutability বজায় রেখে)।

Python
# ========== মডিউল ৫: ক্লায়েন্ট-সাইড Redux-স্টাইল স্টেট স্টোর ==========
def task_reducer(state, action):
    if action["type"] == "AUTH_SUCCESS":
        return {**state, "logged_in_user": action["payload"]["username"], "last_event": "AUTH_SUCCESS"}
    elif action["type"] == "TASK_CREATED":
        return {**state, "tasks": state["tasks"] + [action["payload"]], "last_event": "TASK_CREATED"}
    elif action["type"] == "TASK_REJECTED":
        return {**state, "last_event": "TASK_REJECTED", "last_error": action["payload"]}
    else:
        return state


class Store:
    def __init__(self, reducer, initial_state):
        self.reducer = reducer
        self.state = initial_state
        self._listeners = []

    def dispatch(self, action):
        self.state = self.reducer(self.state, action)
        for listener in self._listeners:
            listener(self.state)
        return self.state

    def subscribe(self, listener):
        self._listeners.append(listener)


def on_state_change(state):
    print(f"  [সাবস্ক্রাইবার] স্টেট পরিবর্তন হয়েছে, last_event={state.get('last_event')}")


client_store = Store(task_reducer, {"logged_in_user": None, "tasks": [], "last_event": None})
client_store.subscribe(on_state_change)

print("client_store-এর প্রাথমিক স্টেট:", client_store.state)

    

৭ · মডিউল ৬ — সম্পূর্ণ এন্ড-টু-এন্ড ফ্লো

এখন সবগুলো টুকরো একসাথে — উপরের পাঁচটি মডিউল ব্যবহার করে একটি সম্পূর্ণ ব্যবহারকারী-যাত্রা ট্রেস করা হবে: (a) রেজিস্ট্রেশন, (b) লগইন করে JWT পাওয়া, (c) টোকেন ছাড়া টাস্ক তৈরির চেষ্টা (প্রত্যাখ্যাত হওয়া উচিত), (d) সঠিক টোকেন দিয়ে টাস্ক তৈরি (সফল হওয়া উচিত), (e) ক্লায়েন্ট-সাইড স্টোরে TASK_CREATED dispatch করা, ও (f) চূড়ান্ত ক্লায়েন্ট-স্টেট প্রিন্ট করা।

Python
# ========== মডিউল ৬: সম্পূর্ণ এন্ড-টু-এন্ড ফ্লো ==========
print("=" * 70)
print("ধাপ (a): নতুন ইউজার রেজিস্টার করা")
resp_register = app.dispatch("POST", "/register", {"body": {"username": "rafiq", "password": "sEcr3t!23"}})
print(resp_register)
assert resp_register["status"] == 201

print("\n" + "=" * 70)
print("ধাপ (b): লগইন করে সত্যিকারের সাইন করা JWT পাওয়া")
resp_login = app.dispatch("POST", "/login", {"body": {"username": "rafiq", "password": "sEcr3t!23"}})
print(resp_login)
assert resp_login["status"] == 200
token = resp_login["body"]["token"]
print(f"প্রাপ্ত টোকেন: {token}")

client_store.dispatch({"type": "AUTH_SUCCESS", "payload": {"username": "rafiq"}})

print("\n" + "=" * 70)
print("ধাপ (c): টোকেন ছাড়া টাস্ক তৈরির চেষ্টা -- প্রত্যাখ্যাত হওয়া উচিত")
resp_no_token = app.dispatch("POST", "/tasks", {"headers": {}, "body": {"title": "প্রথম টাস্ক"}})
print(resp_no_token)
assert resp_no_token["status"] == 401, "টোকেন ছাড়া প্রোটেক্টেড রুট প্রত্যাখ্যান করা উচিত ছিল"

print("\n" + "=" * 70)
print("ধাপ (d): সঠিক টোকেন দিয়ে টাস্ক তৈরি -- সফল হওয়া উচিত")
resp_with_token = app.dispatch("POST", "/tasks", {"headers": {"Authorization": f"Bearer {token}"}, "body": {"title": "প্রথম টাস্ক"}})
print(resp_with_token)
assert resp_with_token["status"] == 201, "সঠিক টোকেন দিয়ে টাস্ক তৈরি সফল হওয়া উচিত ছিল"
created_task = resp_with_token["body"]["task"]

print("\n" + "=" * 70)
print("ধাপ (e): ক্লায়েন্ট Redux-স্টাইল স্টোরে TASK_CREATED অ্যাকশন dispatch করা")
old_state = client_store.state
client_store.dispatch({"type": "TASK_CREATED", "payload": created_task})
new_state = client_store.state
print(f"পুরনো স্টেট অবজেক্ট আইডি: {id(old_state)}")
print(f"নতুন স্টেট অবজেক্ট আইডি:  {id(new_state)}")
print(f"দুটো ভিন্ন অবজেক্ট (immutability বজায় আছে): {old_state is not new_state}")

print("\n" + "=" * 70)
print("ধাপ (f): চূড়ান্ত ক্লায়েন্ট-সাইড স্টেট")
print(client_store.state)
assert client_store.state["logged_in_user"] == "rafiq"
assert any(t["id"] == created_task["id"] for t in client_store.state["tasks"])
print("\nসারমর্ম: টোকেন ছাড়া 401, সঠিক টোকেনে 201, এবং নতুন টাস্কটি এখন ক্লায়েন্ট-স্টেটে সত্যিই উপস্থিত।")

    
এই একটিমাত্র ফ্লোতে কোর্সের পাঁচটি প্যাটার্নই সত্যিকারের কাজ করতে দেখা গেল — app.dispatch() (Router) USERS_DB/TASKS_DB-কে ছুঁয়েছে (CRUD), Task.create() (ORM) দিয়ে নতুন রেকর্ড তৈরি হয়েছে, auth_required মিডলওয়্যার ধাপ (c)-এ সত্যিকারের প্রত্যাখ্যান করেছে ও ধাপ (d)-এ সত্যিকারের অনুমতি দিয়েছে (real HMAC-SHA256 JWT যাচাই দিয়ে), এবং client_store (Redux-স্টাইল) সেই ফলাফল প্রতিফলিত করেছে — সবকিছুই প্রকৃত কম্পিউটেড ফলাফল, কোনো হার্ডকোড করা "simulated output" নয়।
মূল কথা · Key takeaway

একটি ফুল-স্ট্যাক ফ্রেমওয়ার্ক আসলে এইরকম কয়েকটি স্বাধীন কিন্তু আন্তঃসংযুক্ত ধারণার সমষ্টি — রাউটিং যা রিকোয়েস্টকে সঠিক কোডে পাঠায়, একটি ডেটা স্তর (CRUD/ORM) যা তথ্য সংরক্ষণ করে, একটি অথেন্টিকেশন স্তর যা প্রমাণ করে "কে করছে", ও একটি ক্লায়েন্ট-সাইড স্টেট স্তর যা ব্যবহারকারীকে সর্বশেষ তথ্য দেখায়। এই কোর্সে আমরা প্রতিটি ধারণা আলাদা আলাদা করে শিখেছি (M1-M12), আর এই শেষ পাঠে দেখলাম কীভাবে তারা একসাথে একটি বাস্তবসম্মত অ্যাপ্লিকেশনে রূপ নেয় — এখন থেকে যেকোনো নির্দিষ্ট ফ্রেমওয়ার্ক (Express, Django, Rails, Laravel, Spring) শেখার সময় এই একই অন্তর্নিহিত ধারণাগুলোই ভিন্ন সিনট্যাক্সে খুঁজে পাবেন।

ভাবনার প্রশ্ন

প্রতিটি প্রশ্ন নিজে কিছুক্ষণ ভাবুন — তারপর "→ উত্তর" চাপুন।

প্র ০১ ধাপ (c)-এ টোকেন ছাড়া রিকোয়েস্ট 401 পায় কেন, যদিও app.routes-এ POST /tasks-এর জন্য একটি হ্যান্ডলার ঠিকই রেজিস্টার করা আছে?

কারণ রেজিস্টার করা হ্যান্ডলারটি আসল create_task_route নয় — এটি auth_required(create_task_route)-এর রিটার্ন করা wrapped ফাংশন। wrapped() প্রথমে Authorization হেডার চেক করে ও verify_jwt() কল করে; টোকেন অনুপস্থিত থাকলে এটি সাথে সাথে {"status": 401, ...} রিটার্ন করে দেয় — আসল create_task_route লজিকটি একবারও এক্সিকিউট হয় না।

প্র ০২ JWT-এর header ও payload তো শুধু base64url-এনকোড করা — এনক্রিপ্টেড না। তাহলে payload-এ একটি অক্ষর বদলালে কেন verify_jwt() সেটা ধরে ফেলে?

ঠিক ধরেছেন — base64 কোনো এনক্রিপশন নয়, যে কেউ একটি JWT-এর payload পড়তে (ডিকোড করতে) পারে। JWT-এর স্বাক্ষর (signature) গোপনীয়তা (confidentiality) নয়, বরং ইন্টিগ্রিটি (অক্ষতত্ব) নিশ্চিত করে — অর্থাৎ payload তৈরির পর কেউ সেটা বদলায়নি। যেহেতু signature গণনা হয়েছিল মূল (অপরিবর্তিত) header_b64.payload_b64-এর উপর ভিত্তি করে, payload বদলে গেলে verify_jwt()-এর ভেতরে পুনরায় গণনা করা expected_sig_b64 আর টোকেনের আসল signature_b64-এর সাথে মেলে না — তাই hmac.compare_digest() False দেয়।

প্র ০৩ task_reducer-এ কেন সরাসরি state["tasks"].append(...) না করে {**state, "tasks": state["tasks"] + [...]} দিয়ে সম্পূর্ণ নতুন dict রিটার্ন করা হয়েছে?

এটি Redux/Flux প্যাটার্নের একটি মূল নিয়ম — immutability। পুরনো স্টেট অবজেক্ট মিউটেট না করে নতুন একটি তৈরি করলে old_state is not new_state সবসময় নির্ভরযোগ্যভাবে সত্য থাকে (কোড সেলে id() দিয়ে এটাই যাচাই করা হয়েছে)। বাস্তব ফ্রন্ট-এন্ড ফ্রেমওয়ার্কে UI লাইব্রেরিগুলো প্রায়ই এই "রেফারেন্স বদলেছে কি না" পরীক্ষা করেই বুঝতে পারে কখন রি-রেন্ডার করতে হবে — সরাসরি মিউটেট করলে সেই তুলনা ব্যর্থ হয়ে যেত।

অনুশীলন

  1. চিন্তা করুন: ধরুন rafiq একটি সঠিক টোকেন পেয়েছে (ধাপ b-তে), কিন্তু তারপর নিজের পাসওয়ার্ড বদলে ফেলেছে। এই ক্যাপস্টোন টুলকিটে সেই পুরনো টোকেনটি কি এখনও /tasks-এ টাস্ক তৈরি করতে কাজ করবে? কেন হ্যাঁ বা না?

    হ্যাঁ, এখনও কাজ করবে — কারণ verify_jwt() শুধু স্বাক্ষর ও মেয়াদ (এই সরলীকৃত সংস্করণে মেয়াদ চেকই নেই) যাচাই করে, বর্তমান পাসওয়ার্ডের সাথে কোনো তুলনা করে না। এটাই দেখায় বাস্তব প্রোডাকশন সিস্টেমে কেন টোকেন এক্সপায়ারি (মেয়াদ), রিফ্রেশ টোকেন, ও প্রয়োজনে টোকেন রিভোকেশন (বাতিলকরণ) তালিকার মতো অতিরিক্ত ব্যবস্থা প্রয়োজন হয় — শুধু স্বাক্ষর যাচাই যথেষ্ট নয়।

  2. পরীক্ষা করুন: মডিউল ৬-এর কোড সেলে ধাপ (b)-এর ঠিক পরে একটি নতুন লাইন যোগ করুন যা ভুল পাসওয়ার্ড দিয়ে আরেকবার লগইন করার চেষ্টা করে (app.dispatch("POST", "/login", {"body": {"username": "rafiq", "password": "wrongpass"}})) এবং তার রেসপন্স প্রিন্ট করুন — status কি 401 দেখায়?

    হ্যাঁ, status 401 দেখাবে — কারণ login() হ্যান্ডলারে user["password_hash"] != hash_password(password) শর্তটি সত্যি হয়ে যাবে (hash_password("wrongpass") মূল পাসওয়ার্ডের হ্যাশের সাথে মিলবে না), তাই {"status": 401, "body": {"error": "ইউজারনেম বা পাসওয়ার্ড ভুল"}} রিটার্ন হবে — এটি দেখায় লগইন হ্যান্ডলারও ঠিক টাস্ক-তৈরি রুটের মতোই ভুল ইনপুট সঠিকভাবে প্রত্যাখ্যান করে।

আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ

  • কোর্সের সম্পূর্ণ সিলেবাস আবার দেখুন ৫৮টি পাঠ · সম্পূর্ণ অভিনন্দন — Full-Stack Web Frameworks কোর্সের সবগুলো পাঠ এখন সম্পূর্ণ। ফাউন্ডেশন থেকে শুরু করে আর্কিটেকচার প্যাটার্ন, ফ্রন্ট-এন্ড/ব্যাক-এন্ড ফ্রেমওয়ার্ক, স্টেট ম্যানেজমেন্ট, REST API, ORM, অথেন্টিকেশন, রেন্ডারিং স্ট্র্যাটেজি, রিয়েল-টাইম যোগাযোগ, টেস্টিং, ডিপ্লয়মেন্ট, ও এই ক্যাপস্টোন পর্যন্ত — সম্পূর্ণ যাত্রা শেষ হলো।
  • আগের পাঠ L57 কেস স্টাডি: একটি ফুল-স্ট্যাক অ্যাপ্লিকেশন স্কেল করা।
  • Cybersecurity কোর্স সহোদর কোর্স এই পাঠের JWT ও পাসওয়ার্ড হ্যাশিং সহজীকৃত রাখা হয়েছে শেখার জন্য — বাস্তব প্রোডাকশন-গ্রেড সিকিউরিটি প্র্যাকটিস (bcrypt, টোকেন রিভোকেশন, রেট-লিমিটিং) সেই কোর্সে বিস্তারিত আলোচিত।
  • সব Courses দেখুন ABCL TECH C, C++, Python, Java, JavaScript, DSA, DBMS, Discrete Mathematics, System Design, Cybersecurity, Cloud Computing & DevOps, Computer Networks, Operating Systems, Computer Architecture, Programming Languages & Compiler Design, Software Engineering & Git, Theory of Computation, Engineering Economics ও Full-Stack Web Frameworks — সব এক জায়গায়।
আগের পাঠ
কেস স্টাডি: একটি ফুল-স্ট্যাক অ্যাপ্লিকেশন স্কেল করা