MAD Study Hub

Home / MAD2 / Week 12

Week 12 · The project: putting it all together

Official syllabus line: Project
About 90 minEnd Term material (see exam radar)BSCS2006 (+ project BSCS2006P) · Thejesh G N, Nitin Chandrachoodan
Before you start: MAD2 weeks 1 to 11 (Vue, Vuex, tokens, Celery, Redis, caching, messaging). MAD1 project helps.

The one-minute story

Think of it like this

Building a small restaurant, not just cooking one dish. Until now you practised single dishes: a Flask route, a Vue component, a Celery task, a cache. The project is the whole restaurant: a kitchen (Flask API and database), a dining room (Vue front end), a waiter who checks reservations (token login and roles), a clock that rings at closing time to tidy up (Celery beat), a pantry with ready-made sauces (Redis cache), and a place to test the fire alarm without a real fire (MailHog).

A restaurant is not judged by how fancy one dish is. It is judged by whether guests are seated, served and billed correctly, and whether the owner can explain how it all works. That last part is your viva.

Tip

Honest note on scope. This lesson is a guide, not an official rulebook. The official site lists only the single word "Project" for this week. The project statement of your term (published on the course page) is the only authority on what is required and what is optional. The lists below come from the course material and what students report; if the two differ, follow the statement and ask on the course forum.

By the end of this week you can:

  • draw the architecture of a MAD2 project and say what each part is for;
  • plan the folders, database tables, API routes and Vue structure for a given statement;
  • explain the token flow and implement a role guard on the server and in the router;
  • schedule Celery jobs (daily reminder, monthly report, async CSV export) and cache with expiry;
  • run everything locally on Windows, test it, and prepare the README, report and submission;
  • check your own project against a rubric and avoid the common mistakes.

Words you need

Words you need
  • Architecture - the big picture: which programs exist and how they talk.
  • API-driven - the backend only serves data (JSON); the front end renders it.
  • SPA (single page application) - one HTML page; Vue swaps components without full reloads.
  • Token - a string the server gives after login; the client sends it with every request.
  • RBAC (role-based access control) - what you may do depends on your role (admin, user...).
  • Guard - a check that blocks access: a decorator on the server, beforeEach in the router.
  • Blueprint - a way to split a Flask app into modules.
  • OpenAPI / Swagger - a standard file describing your API routes (YAML), used as documentation.
  • CORS - the browser rule that blocks a page from one origin from calling another unless the server allows it.
  • Celery worker / beat - the process that runs tasks / the scheduler that starts them on time.
  • MailHog - a fake mail server for local testing.
  • WSL - Windows Subsystem for Linux: a Linux inside Windows, convenient for Redis and Celery.
  • Seed data - starter rows (the admin user) created on first run.
  • Viva - the oral examination on your own project.

Idea 1: The architecture of a typical project

 Browser (Vue 2 SPA: components, Vue Router, Vuex)
        |  HTTP + JSON, header  Authorization: Bearer <token>
        v
 Flask API (routes, auth, roles, validation)  ---- SQLite (SQLAlchemy models)
        |                       \
        | cache get/set          \ task.delay()
        v                         v
     Redis  <------------------ Celery worker  ----> SMTP: MailHog (dev) or real server
   (cache + broker)                 ^
                                    | schedule
                              Celery beat (daily / monthly)
PartJobTypical tools
Front endpages, forms, calls the APIVue 2, Vue Router 3, Vuex 3, fetch
Backendbusiness rules, auth, JSONFlask, Flask-RESTful or plain routes, Flask-SQLAlchemy
Databasedurable dataSQLite
Rediscache and Celery brokerRedis, Flask-Caching
Celery workersslow or scheduled jobsCelery, delay(), beat
Mailreminders, reportsFlask-Mail, MailHog

Core versus optional (as students report)

Typical features reported by students for MAD2 projects:

Usually coreOften optional or bonus
Role-based access (for example admin and user)Scheduled jobs: daily reminder, monthly report
Token login (not only server sessions)CSV export (async, with a download)
CRUD for the main entitiesCaching with expiry
Backend as an API, a Vue front endPDF report, extra charts, search

The point to remember: read your term's project statement. Marks follow it. If it makes a feature mandatory, it is mandatory, whatever this table says.

The idea

A good MAD2 project is small but complete: every required feature works end to end, the code is organised, secrets are not in the repository, and you can explain every line. Ten half-working features lose to five solid ones.

Your turn: 1. Draw the boxes

Your statement says: "Librarians add books; members request books; members get a reminder mail every evening if a book is due tomorrow; librarians can export all issued books as CSV." List the processes you need to run on your laptop to demo this.

Need a hint?

Think: web, data, background, schedule, mail, broker.

Show the full solution

(1) Flask app (API). (2) Vue front end (dev server or built files). (3) Redis (broker, and cache if used). (4) Celery worker. (5) Celery beat. (6) MailHog. SQLite is just a file, no process. That is why a demo needs several terminals: write a README run order.

Idea 2: Folders, database and API plan

Folder structure

project/
  backend/
    app.py               # create_app(), registers extensions and blueprints
    config.py            # settings read from environment
    extensions.py        # db, jwt, cache, mail, celery objects
    models.py            # SQLAlchemy models
    routes/
      auth.py            # /api/login, /api/register
      books.py           # CRUD
      admin.py           # admin-only routes
    tasks.py             # Celery tasks and beat schedule
    templates/email/     # reminder.html, report.html
    requirements.txt
  frontend/
    src/
      main.js
      router/index.js
      store/index.js     # Vuex
      api.js             # fetch wrapper that adds the token
      components/ views/
    package.json
  README.md
  openapi.yaml           # API description
  report.pdf

Why: extensions.py avoids circular imports; routes in blueprints keep each file short; templates for emails keep HTML out of Python code. A simpler layout (a single app.py) is fine for a small project, as long as it is tidy.

Database design: design from the statement

Underline nouns in the statement: they become tables. Underline "has many": they become foreign keys. Take the library statement above.

TableColumnsNotes
userid, username (unique), email, password_hash, role, last_visitrole is admin or member
bookid, title, author, available (bool)index on title if you search
issueid, user_id (FK), book_id (FK), issued_on, due_on, returned_on (nullable)one row per loan; a many-to-many relation turned into a table
class User(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(50), unique=True, nullable=False)
    email = db.Column(db.String(120), nullable=False)
    password_hash = db.Column(db.String(200), nullable=False)
    role = db.Column(db.String(10), nullable=False, default="member")

class Issue(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    user_id = db.Column(db.Integer, db.ForeignKey("user.id"), nullable=False, index=True)
    book_id = db.Column(db.Integer, db.ForeignKey("book.id"), nullable=False)
    due_on = db.Column(db.Date, nullable=False)
    returned_on = db.Column(db.Date)

Store a hash (werkzeug.security.generate_password_hash) never the password. Create the admin by seeding on first run, not through a public register page.

API route plan

Use nouns, the right verb, the right status code (the earlier weeks).

Method and pathWhoSuccessErrors
POST /api/loginanyone200 + token401
POST /api/registeranyone201400, 409
GET /api/books?page=1logged in200401
POST /api/booksadmin201400, 401, 403
PUT /api/books/<id>admin200404
DELETE /api/books/<id>admin204404
POST /api/books/<id>/issuemember201409 if unavailable
POST /api/exportadmin202 + task id
GET /api/export/<task_id>admin200 state / file

OpenAPI. Describe the same table in openapi.yaml; it doubles as documentation for the examiner:

openapi: 3.0.0
info: {title: Library API, version: "1.0"}
paths:
  /api/books:
    get:
      summary: List books (paginated)
      parameters:
        - {name: page, in: query, schema: {type: integer}}
      responses:
        "200": {description: A page of books}
        "401": {description: Missing or bad token}
Solved example: 1. From statement to plan

Statement. "Doctors add available slots; patients book a slot; a patient gets a mail the evening before the appointment."

Solution. Nouns: user (role doctor or patient), slot, booking. Tables: user, slot(id, doctor_id FK, starts_at, booked bool), booking(id, slot_id FK unique, patient_id FK). Unique slot_id in booking stops double booking; a second attempt returns 409. Routes: POST /api/slots (doctor, 201), GET /api/slots?page=1 (patient), POST /api/slots/<id>/book (patient, 201 or 409). Job: beat at 18:00 mails patients whose slot starts tomorrow.

Your turn: 2. Design tables and routes

Statement: "A quiz platform. Admin creates quizzes with questions. Students attempt a quiz and see their score. Admin sees all scores. Students get a daily mail if they have not attempted any quiz today." Give the tables with keys and eight routes.

Need a hint?

Nouns: user, quiz, question, attempt. An attempt links a user and a quiz and holds a score.

Show the full solution

Tables: user(id, username, email, password_hash, role); quiz(id, title, created_by FK user); question(id, quiz_id FK, text, options JSON/text, correct_index); attempt(id, user_id FK, quiz_id FK, score, taken_at).

Routes: POST /api/login (200/401); GET /api/quizzes (any user); POST /api/quizzes (admin, 201); POST /api/quizzes/<id>/questions (admin, 201); GET /api/quizzes/<id> (questions without answers, user); POST /api/quizzes/<id>/attempts (student, body answers, returns score, 201); GET /api/me/attempts (student); GET /api/admin/attempts (admin). Daily reminder: Celery beat task queries users with no attempt today and mails them.

Idea 3: Token login, roles and guards

The idea

Token flow.

  1. The Vue login form sends POST /api/login with username and password (HTTPS in real life).
  2. Flask checks the password hash and replies with a token (for example a JWT that carries the user id, role and an expiry).
  3. Vue stores the token (Vuex, and localStorage if the user should stay logged in after reload).
  4. Every later request sends the header Authorization: Bearer <token>.
  5. Flask checks the token on each protected route; a missing or bad token gives 401; a valid token with the wrong role gives 403.
  6. Logout: delete the token in the client. A token also expires on its own.

401 vs 403: 401 means "I do not know who you are" (no or invalid token). 403 means "I know who you are, and you may not do this".

Server side with Flask-JWT-Extended, including a role decorator (I ran this with the test client: no token gives 401, a student token on the admin route gives 403, an admin token gives 200):

from datetime import timedelta
from functools import wraps
from flask import Flask, jsonify, request
from flask_jwt_extended import JWTManager, create_access_token, jwt_required, get_jwt

app = Flask(__name__)
app.config["JWT_SECRET_KEY"] = "change-me-and-read-from-environment"
app.config["JWT_ACCESS_TOKEN_EXPIRES"] = timedelta(hours=1)
JWTManager(app)

@app.post("/api/login")
def login():
    d = request.get_json()
    user = find_user(d.get("username"))                 # your own lookup
    if not user or not check_password(user, d.get("password")):
        return jsonify(error="bad credentials"), 401
    token = create_access_token(identity=user.username, additional_claims={"role": user.role})
    return jsonify(token=token, role=user.role)

def role_required(role):
    def deco(fn):
        @wraps(fn)
        @jwt_required()
        def wrapper(*a, **k):
            if get_jwt().get("role") != role:
                return jsonify(error="forbidden"), 403
            return fn(*a, **k)
        return wrapper
    return deco

@app.get("/api/admin/stats")
@role_required("admin")
def stats():
    return jsonify(users=2)

Your course may use a different token library (for example Flask-Security token authentication). The flow is the same: use what the lectures and the statement ask for.

Vue side: Vuex store, API helper, router guard

// store/index.js (Vuex 3)
import Vue from "vue";
import Vuex from "vuex";
Vue.use(Vuex);

export default new Vuex.Store({
  state: {
    token: localStorage.getItem("token") || null,
    role: localStorage.getItem("role") || null
  },
  mutations: {
    setAuth(state, { token, role }) {
      state.token = token; state.role = role;
      localStorage.setItem("token", token); localStorage.setItem("role", role);
    },
    clearAuth(state) {
      state.token = null; state.role = null;
      localStorage.removeItem("token"); localStorage.removeItem("role");
    }
  },
  actions: {
    async login({ commit }, creds) {
      const res = await fetch("http://localhost:5000/api/login", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify(creds)
      });
      if (!res.ok) throw new Error("Login failed");
      commit("setAuth", await res.json());
    }
  }
});
// api.js
import store from "./store";
export function api(path, options = {}) {
  return fetch("http://localhost:5000" + path, {
    ...options,
    headers: {
      "Content-Type": "application/json",
      Authorization: "Bearer " + store.state.token,
      ...(options.headers || {})
    }
  });
}
// router/index.js (Vue Router 3)
import VueRouter from "vue-router";
import store from "../store";

const routes = [
  { path: "/login", component: Login },
  { path: "/books", component: Books, meta: { requiresAuth: true } },
  { path: "/admin", component: Admin, meta: { requiresAuth: true, role: "admin" } }
];
const router = new VueRouter({ routes });

router.beforeEach((to, from, next) => {
  if (to.meta.requiresAuth && !store.state.token) return next("/login");
  if (to.meta.role && to.meta.role !== store.state.role) return next("/books");
  next();
});
export default router;
Watch out - real exam trap
  • A client-side guard is only for convenience. A user can edit localStorage and open /admin. The server must check the role on every protected route. This is the most asked viva question.
  • Do not keep the signing secret, the SMTP password or webhook URLs in Vue code, or in Git.
  • A token is not encrypted by default (a JWT is only signed): do not put the password in it.
  • Do not use GET for login with a password in the URL.
  • A guard that checks only on page load misses expiry: handle a 401 response by clearing the token and redirecting to login.
Solved example: 2. One request through the whole system

Question. An admin clicks "Export CSV". Trace what happens.

Solution. (1) Vue calls POST /api/export with the Bearer token. (2) Flask checks the token and the admin role (else 401 or 403). (3) It calls export_csv.delay(), so a message goes into Redis, and replies 202 with a task id. (4) A Celery worker takes the message and writes the file. (5) The page polls GET /api/export/<id> every few seconds. (6) On SUCCESS the page shows a download link.

Your turn: 3. 401 or 403

(a) No Authorization header on /api/books. (b) A student token on POST /api/books (admin only). (c) Admin token on GET /api/books/9999 which does not exist. (d) A student edits localStorage.role to admin and opens /admin in Vue; what stops the data from leaking?

Need a hint?

Is it identity, permission, or existence?

Show the full solution

(a) 401. (b) 403. (c) 404. (d) The Vue guard is fooled, but the API still checks the role claim inside the signed token, which the student cannot change, so the admin API returns 403 and no data leaks.

Idea 4: Celery jobs, caching and CORS in the project

Celery jobs

Three typical jobs (details in week 10):

JobTriggerNotes
Daily reminderbeat, crontab(hour=18, minute=0)query who needs it, send an HTML mail
Monthly reportbeat, crontab(day_of_month=1, hour=8, minute=0)Jinja2 to HTML (optionally PDF), mail it
Async CSV exportuser clicks a button, export.delay()API returns 202 with a task id; page polls GET /api/export/<task_id>; then offers the download

Two design questions from past papers: for 1000 users, should the report job be one task per user or one task that loops? One task per user can spread over many workers; one big task is simpler but runs in series. Either can be right; state the trade-off. And for a CSV that takes 20 to 40 seconds, the front end should poll the task state (say every 3 to 5 seconds), not wait a guessed fixed time.

from celery.result import AsyncResult

@app.post("/api/export")
@role_required("admin")
def start_export():
    task = export_csv.delay()
    return jsonify(task_id=task.id), 202

@app.get("/api/export/<task_id>")
@role_required("admin")
def export_status(task_id):
    r = AsyncResult(task_id, app=celery)
    return jsonify(state=r.state, file="/static/export.csv" if r.state == "SUCCESS" else None)

Caching with expiry

Cache the read endpoints that many users repeat and that tolerate a little staleness, such as a summary or a book list. Use a TTL and delete the key when data changes (see week 11). Say your numbers: "book list, 60 seconds, deleted on add or delete".

CORS

The Vue dev server (http://localhost:8080) and Flask (http://localhost:5000) are different origins (different port), so the browser blocks the call unless Flask allows it. Install flask-cors:

from flask_cors import CORS
CORS(app, resources={r"/api/*": {"origins": ["http://localhost:8080"]}})

I tested that a request carrying Origin: http://localhost:8080 receives Access-Control-Allow-Origin: http://localhost:8080. Allow only your front end origin, not * in the final version. If the browser sends an Authorization header, it first sends an OPTIONS preflight; flask-cors answers it.

Idea 5: Run it, test it, document it

Running everything on Windows

PieceWindows way
Flaskpython -m venv venv, venv\Scripts\activate, pip install -r requirements.txt, flask run
Vuenpm install, npm run serve
Redisnot native on Windows: install WSL (wsl --install), then sudo apt install redis-server and redis-server; reachable from Windows on localhost:6379 in most setups
Celery workerin WSL: celery -A app.celery worker -l info. On plain Windows it needs -P solo (or -P threads)
Celery beatcelery -A app.celery beat -l info
MailHogrun the downloaded .exe; SMTP 1025, inbox http://localhost:8025

If one of them does not start, the demo breaks, so rehearse the start-up order: Redis, MailHog, Flask, worker, beat, Vue. Write it in the README as numbered steps. Keep a requirements.txt (pip freeze > requirements.txt).

Testing

Test the backend with pytest and Flask's test client (no server needed):

def test_admin_route_needs_admin(client, student_token, admin_token):
    h = lambda t: {"Authorization": "Bearer " + t}
    assert client.get("/api/admin/stats").status_code == 401
    assert client.get("/api/admin/stats", headers=h(student_token)).status_code == 403
    assert client.get("/api/admin/stats", headers=h(admin_token)).status_code == 200

Test login (good and bad password), one CRUD path, one 404, and one validation error (400). Test the Celery task by calling the function directly (export_csv()), without a worker.

README and report checklist

  • README: what the project is, how to install, exact run order, default admin login, ports, how to run tests, optional features and where they are.
  • Report (short; use the template your term gives): statement, architecture diagram, database ER diagram, API list (or openapi.yaml), key design decisions, screenshots, how features map to the statement.
  • Never commit venv, node_modules, .env, real passwords or webhook URLs.

Submission checklist

  1. Project statement requirements ticked one by one.
  2. Fresh clone works: new virtual environment, pip install -r requirements.txt, npm install, run order in the README.
  3. Seed admin exists; sample data loads.
  4. The .zip or repository is within the stated size and format; correct file names.
  5. Report and screen-recording or links (if the term asks) in the right place before the deadline.
  6. You can start every process within two minutes.
Your turn: 4. Find the problems

A project has: the admin check only in Vue, the SMTP password in app.py, the Celery task sending mail for 1000 users inside the /send route, no requirements.txt, and CORS origins="*". Fix each in one line.

Need a hint?

One line each; think server, secrets, async, reproducibility, scope.

Show the full solution

(1) Check the role on the server too (decorator). (2) Move the password to an environment variable. (3) Call task.delay() and return 202. (4) pip freeze > requirements.txt. (5) Allow only the front-end origin.

In your project

Self-review rubric (score yourself 0, 1 or 2 per row; below 14 of 20 means fix before submitting).

Row2 means
Requirementsevery item of the statement works end to end
Auth and rolestoken login; server checks roles; sensible 401 and 403
API and dataclean routes, status codes, validation, hashed passwords, indexes where used
Front endcomponents, router, store; loading and error states shown
Background jobsat least the required jobs run and can be shown on demand
Performancepagination; cache with TTL and invalidation (if required)
Code qualityfolders, naming, no secrets, comments where it is not obvious
Testskey paths covered; tests pass
DocumentationREADME, report, API description
Viva readinessyou can explain every file and change something live

Idea 6: A six-week timeline, common mistakes and the viva

A project timeline of 6 weeks

WeekGoalDone when
1Read the statement, list features, design tables and routesa one-page plan; a repository with folders
2Backend: models, seed, login, token, roleslogin works in a REST client
3Backend: CRUD, validation, errors, paginationall required routes pass tests
4Vue: router, store, login, main pagesa user can log in and use the core flow
5Redis cache, Celery jobs, mail, CSV exportjobs shown with MailHog; cache has TTL
6Tests, README, report, rehearse the viva, submit earlyfresh-clone test passes

Start week 5 early if scheduled jobs are mandatory; they need the most set-up time.

Common mistakes

  • Building features not in the statement before the required ones.
  • Secrets in Git (token key, SMTP password, webhook URL).
  • Role checks only in the front end.
  • Plain-text passwords; SQL built by string formatting.
  • Heavy work inside a request (mail, PDF, CSV).
  • Cache with no expiry and no invalidation; or caching personalised data under a shared key.
  • Works only on your laptop (missing requirements.txt, hard-coded paths and ports).
  • Forgetting CORS, then discovering it on demo day.
  • Not understanding code taken from tutorials or generated by tools. The viva will find out.

How this connects to the viva

The project viva is you walking the examiner through your own work. Questions follow your code: the architecture, why this table, how login works, what the cache stores, how the job is scheduled, and "change X live". Practise with the question banks: project viva and MAD2 viva.

Your turn: 5. Be the examiner

Write five questions an examiner could ask about your own project, and the one-sentence answer for each.

Need a hint?

Use: architecture, auth, database, job, cache.

Show the full solution

Example set: (1) "Draw your architecture." (Flask API, SQLite, Vue, Redis, Celery worker and beat, MailHog.) (2) "How does a request prove who it is?" (Bearer token on every call, checked by the server.) (3) "Why is the report generated in Celery?" (Slow, must not block the request, scheduled by beat.) (4) "What happens to your cache when a book is added?" (The list key is deleted so the next read refreshes it.) (5) "Add a field isbn to books now." (Add the column, update the serializer and form; delete or migrate the old database file.)

Exam radar

Exam radar

The evidence for this week is thin. About 4 distinct questions matched; keyword counts are roughly 11 of 1471 End Term, 6 of 848 Quiz 1 and 0 of 608 Quiz 2 questions. Weeks 10 to 12 have few matched questions, so this is not a topic with its own question bank: the project is assessed in the viva and report, and the written papers test the ingredients from other weeks.

What the few matched questions looked like:

  • A Vue v-for list from the project-style data (predict what is rendered).
  • A UI state question after a series of clicks on tasks.
  • Celery design: generating 1000 reports with one task per user versus one task looping over users.
  • CSV generation in Celery taking 20 to 40 seconds, and how the front end should wait: poll the task state, not a guessed setTimeout.

So revise the project ingredients: token auth, roles, Celery and beat, Redis cache, CORS (an origin is scheme, host and port: a script from http://example.com cannot call http://new.example.com unless allowed), plus week 10 and week 11.

Watch out - real exam trap
  • Treating the project as "extra". It carries its own marks and the viva.
  • Using a fixed delay instead of polling for an async job.
  • Forgetting that different subdomains and different ports are different origins.
  • Mixing up 401 and 403.

Quick quiz

Quick quiz

A student edits localStorage so the Vuex role is admin and opens the admin page. The admin API route checks the role claim in the signed token. What happens when the page calls the API?

  • The data is shown, because the front end said admin.
  • The API returns 403, because the signed token still carries the role member.
  • The API returns 404.
  • The API returns 200 but with empty data.
The server must never trust the client; the real role is inside the signed token.

Which design is best for an export that takes 20 to 40 seconds?

  • Compute it inside the request and increase the browser timeout.
  • Wait 41 seconds with setTimeout and then fetch the file.
  • Start a Celery task, return 202 with a task id, and poll the status until it is SUCCESS.
  • Ask the user to refresh until it works.
Do heavy work in a background job and check its state; fixed delays are guesses.

Which processes does a demo of "daily reminder mail" need running, in addition to Flask?

  • Only MailHog.
  • Redis, a Celery worker, Celery beat and MailHog.
  • Only the Vue server.
  • Only SQLite.
The beat starts the task, Redis carries it, the worker runs it, MailHog shows the mail.

Select all statements that are true. (select all that apply)

  • A route guard in Vue improves the user experience but is not real security.
  • The Vue dev server on port 8080 and Flask on 5000 are different origins, so CORS must be configured.
  • A 403 means the token is missing.
  • A password may be stored in plain text if the database is local.
403 means authenticated but not allowed; passwords must be hashed.

Which belongs in .gitignore (not in the repository)?

  • requirements.txt
  • README.md
  • .env with the SMTP password and venv/
  • models.py
Secrets and generated environments are never committed; the others are needed to rebuild the project.

Which statement about the project is the most accurate?

  • This lesson's list of optional features overrides the project statement.
  • The project statement of the term is the authority on what is required.
  • Only the front end is marked.
  • Features beyond the statement always earn extra marks.
Marks follow the statement; extras do not replace required items.

Viva: say it out loud

Viva: say it out loud
Draw and explain the architecture of your project.

The Vue single page app calls the Flask JSON API with a bearer token. Flask uses SQLAlchemy with SQLite for data and Redis for caching. Slow and scheduled work runs in Celery workers, started by Celery beat, with Redis as the broker. In development MailHog catches the emails.

How does login work?

The form sends the username and password to the login route. Flask checks the password hash and returns a signed token with the user and role and an expiry. Vue keeps it in the Vuex store and localStorage, and sends it as a Bearer header on every call. The server verifies it on each protected route.

How do you stop a normal user from using admin features?

The router guard hides the pages, but the real check is on the server: an admin-only decorator reads the role from the signed token and returns 403 otherwise. The client cannot change the signed token.

Why did you design the tables this way?

Each noun in the statement became a table, and the relations became foreign keys, such as an issue table linking user and book. Passwords are hashed, the role is a column, and I added indexes on columns I filter by.

What does your Celery beat do, and how can you show it now?

It runs the daily reminder at 6 pm and the monthly report on the first of the month. I can trigger the same task by hand with delay(), and open MailHog to show the mail, then show the schedule entry in the code.

What do you cache and how do you invalidate it?

The book list and dashboard summary, in Redis through Flask-Caching with a 60 second timeout. When a book is added or changed, the key is deleted so the next request rebuilds it.

What would you change with more time?

I would add more tests and a refresh-token flow, move from SQLite to a server database for many users, and improve error messages. Being honest about limits is better than pretending the project is perfect.

Change something live: add a field to a model.

Add the column in models.py, update the serializer and the form, and recreate or migrate the database (for a small project, delete the SQLite file and re-seed). Then show it in the API response and the page.

Recap

Remember this
  • The project is the whole system: Vue SPA, Flask API, SQLite, Redis, Celery worker and beat, MailHog.
  • Your term's project statement decides what is required. Typical core: roles, token login, CRUD, API backend. Typical optional: scheduled jobs, CSV export, caching.
  • Design from the statement: nouns to tables, relations to foreign keys, routes with verbs and status codes, an OpenAPI file.
  • Server-side role checks always; client guards are convenience. 401 is unknown, 403 is forbidden.
  • Heavy work goes to Celery; the page polls task state; caches have TTL and invalidation; CORS allows only your front end.
  • Prepare a README with run order, report, tests, a fresh-clone check and a 6 week plan. Rehearse the viva.
Code and facts to keep
  • Header: Authorization: Bearer <token>; 401 = no or bad token, 403 = not allowed, 409 = conflict, 202 = accepted for background work
  • Flask: create_access_token(identity=..., additional_claims={"role": ...}), @jwt_required(), get_jwt()
  • Vue router guard: router.beforeEach((to, from, next) => { ... next("/login") })
  • Vuex: state, mutations (sync), actions (async), commit, dispatch
  • CORS: CORS(app, resources={r"/api/*": {"origins": ["http://localhost:8080"]}})
  • Beat: crontab(hour=18, minute=0), crontab(day_of_month=1, hour=8, minute=0)
  • Celery on Windows: celery -A app.celery worker -l info -P solo; beat: celery -A app.celery beat -l info
  • MailHog: SMTP 1025, inbox http://localhost:8025
  • Run order: Redis, MailHog, Flask, worker, beat, Vue
  • pip freeze > requirements.txt; keep secrets in .env, never in Git
  • Related: week 10, week 11, project viva, MAD2 viva
Connected weeks (learn them together)

Practise with real exam questions

These come straight from past papers (with the real figures and the official answer key). Open 34 real questions on this week   All End Term papers