Files
tonehaus/docs/assignment2-project-briefing.md
T
2026-04-02 15:42:38 +01:00

27 KiB
Raw Blame History

Tonehaus Project Briefing

Purpose

Tonehaus is a Symfony 7 music album review application extended for the Assignment 2 RESTful API brief. It combines a server-rendered website with a versioned JSON API, JWT authentication, external music metadata providers, and generated API documentation. The project is designed so the web UI and API share the same domain model and service layer rather than existing as separate systems.

Core Stack

  • Symfony 7
  • PHP with Doctrine ORM
  • Twig templates
  • SQLite by default, with Postgres support
  • Guzzle for external API consumption
  • LexikJWTAuthenticationBundle for JWT auth
  • FOSRestBundle for API response handling
  • NelmioApiDocBundle for OpenAPI documentation
  • PHPUnit for functional and unit tests

Main Domain Model

  • User: authenticated account with ROLE_USER, ROLE_MODERATOR, or ROLE_ADMIN
  • Album: normalized album record, either external or user-created
  • AlbumTrack: persisted track list for each album
  • Review: user-authored review with title, content, rating, and timestamps
  • Setting: key-value storage for site configuration and provider credentials

What The Application Does

Web Application

  • lets users search for albums
  • shows album pages with cover art, metadata, tracklists, review averages, and reviews
  • allows logged-in users to create reviews from the album page
  • allows users to create, edit, and delete their own user-created albums
  • provides profile, password, and settings pages
  • gives moderators/admins dashboards for moderation and site administration
  • includes admin settings for external provider credentials and registration control

REST API

The API is versioned under /api/v1 and returns JSON.

Implemented endpoints:

  • GET /api/v1
  • POST /api/v1/auth/login
  • GET /api/v1/albums
  • POST /api/v1/albums/ingest
  • GET /api/v1/albums/{id}
  • GET /api/v1/albums/{id}/reviews
  • GET /api/v1/reviews
  • GET /api/v1/reviews/{id}
  • POST /api/v1/reviews
  • PUT /api/v1/reviews/{id}
  • PATCH /api/v1/reviews/{id}
  • DELETE /api/v1/reviews/{id}

Automatic OpenAPI documentation is available at:

  • /api/docs
  • /api/v1/docs
  • /api/docs.json
  • /api/v1/docs.json

API Endpoint Reference

This section documents every implemented API endpoint briefly, with purpose, authentication, key inputs, and the main response shape.

Example values below are representative of the current implementation and use realistic sample IDs such as tidal:251380836 and numeric review IDs.

GET /api/v1

  • purpose: API index and discovery endpoint
  • auth: none
  • returns: API name, version, endpoint groups, top-level links, and top-level actions
  • HATEOAS: exposes self, auth_login, albums, reviews, docs, docs_v1, schema, and schema_v1

Example request:

GET /api/v1
Accept: application/json

Example response:

{
  "name": "Tonehaus API",
  "version": "v1",
  "endpoints": {
    "auth": ["POST /api/v1/auth/login"],
    "albums": [
      "GET /api/v1/albums",
      "POST /api/v1/albums/ingest (JWT)",
      "GET /api/v1/albums/{id}",
      "GET /api/v1/albums/{id}/reviews"
    ],
    "reviews": [
      "GET /api/v1/reviews",
      "GET /api/v1/reviews/{id}",
      "POST /api/v1/reviews (JWT)",
      "PUT /api/v1/reviews/{id} (JWT)",
      "PATCH /api/v1/reviews/{id} (JWT)",
      "DELETE /api/v1/reviews/{id} (JWT)"
    ]
  },
  "links": {
    "self": "/api/v1",
    "auth_login": "/api/v1/auth/login",
    "albums": "/api/v1/albums",
    "reviews": "/api/v1/reviews",
    "docs": "/api/docs"
  },
  "actions": {
    "login": {
      "method": "POST",
      "href": "/api/v1/auth/login"
    }
  }
}

POST /api/v1/auth/login

  • purpose: authenticate a user and issue a JWT for protected routes
  • auth: none
  • request body: email, password
  • success response: 200 with { "token": "..." }
  • error response: 401 for invalid credentials

Example request:

POST /api/v1/auth/login
Content-Type: application/json
Accept: application/json

{
  "email": "user@example.com",
  "password": "password123"
}

Example success response:

{
  "token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9..."
}

Example error response:

{
  "error": {
    "code": "invalid_credentials",
    "message": "Invalid credentials.",
    "status": 401
  }
}

GET /api/v1/albums

  • purpose: search albums from the local catalog and the currently active external provider
  • auth: none
  • query parameters: query, album, artist, genre, year_from, year_to, source, limit, mode
  • returns:
    • meta with source, limit, query, mode, and search profile
    • data as album resources
    • stats for review aggregates
    • saved_ids for already-ingested external albums
    • collection-level links and actions
  • HATEOAS:
    • collection links include self, root, and ingest
    • action metadata includes ingest_album
    • each album resource is decorated with its own links and actions

Example request:

GET /api/v1/albums?query=4%3A44&artist=JAY-Z&limit=5&mode=fast
Accept: application/json

Example response:

{
  "meta": {
    "source": "all",
    "limit": 5,
    "query": "4:44",
    "mode": "fast"
  },
  "data": [
    {
      "id": "tidal:251380836",
      "name": "4:44",
      "images": [
        { "url": "https://cdn.example.test/320.jpg" }
      ],
      "artists": [
        { "name": "JAY-Z" }
      ],
      "genres": ["Hip Hop"],
      "release_date": "2017-06-30",
      "total_tracks": 10,
      "external_urls": {
        "external": "https://tidal.com/browse/album/251380836"
      },
      "source": "external",
      "links": {
        "self": "/api/v1/albums/tidal%3A251380836",
        "reviews": "/api/v1/albums/tidal%3A251380836/reviews",
        "open_source": "https://tidal.com/browse/album/251380836"
      },
      "actions": {
        "create_review": {
          "method": "POST",
          "href": "/api/v1/reviews",
          "requires_auth": true,
          "payload": {
            "album_id": "tidal:251380836"
          }
        },
        "ingest": {
          "method": "POST",
          "href": "/api/v1/albums/ingest",
          "requires_auth": true,
          "payload": {
            "external_id": "tidal:251380836"
          }
        }
      }
    }
  ],
  "stats": {
    "tidal:251380836": {
      "count": 2,
      "avg": 8.5
    }
  },
  "saved_ids": ["tidal:251380836"],
  "links": {
    "self": "/api/v1/albums?source=all&limit=5&mode=fast&query=4%3A44&artist=JAY-Z",
    "root": "/api/v1",
    "ingest": "/api/v1/albums/ingest"
  },
  "actions": {
    "ingest_album": {
      "method": "POST",
      "href": "/api/v1/albums/ingest",
      "requires_auth": true
    }
  }
}

POST /api/v1/albums/ingest

  • purpose: persist an external album into the local database
  • auth: JWT required
  • request body:
    • either external_id
    • or query
    • or artist and album
  • success response: 201 with data for the ingested album and tracks
  • main error responses:
    • 400 invalid JSON or missing lookup input
    • 401 missing/invalid JWT
    • 404 external album not found
    • 422 album missing required metadata or tracks

Example request:

POST /api/v1/albums/ingest
Authorization: Bearer <jwt-token>
Content-Type: application/json
Accept: application/json

{
  "external_id": "tidal:251380836"
}

Example response:

{
  "data": {
    "id": "tidal:251380836",
    "name": "4:44",
    "artists": [
      { "name": "JAY-Z" }
    ],
    "genres": ["Hip Hop"],
    "release_date": "2017-06-30",
    "total_tracks": 10,
    "source": "external",
    "links": {
      "self": "/api/v1/albums/tidal%3A251380836",
      "reviews": "/api/v1/albums/tidal%3A251380836/reviews"
    }
  },
  "tracks": [
    {
      "id": "track-1",
      "disc": 1,
      "track": 1,
      "name": "Kill Jay Z",
      "duration_ms": 205000
    }
  ]
}

GET /api/v1/albums/{id}

  • purpose: fetch one album by public identifier
  • auth: none
  • path parameter: id string public album ID such as a provider ID or user album ID
  • returns:
    • data for one album
    • tracks list
    • stats with review count and average rating
  • behaviour:
    • if the album is not yet local but is available from the external provider, it can be hydrated and persisted automatically
  • HATEOAS on data:
    • links include self, reviews, and open_source when available
    • actions include create_review
    • external albums also expose ingest

Example request:

GET /api/v1/albums/tidal%3A251380836
Accept: application/json

Example response:

{
  "data": {
    "id": "tidal:251380836",
    "name": "4:44",
    "images": [
      { "url": "https://cdn.example.test/640.jpg" }
    ],
    "artists": [
      { "name": "JAY-Z" }
    ],
    "genres": ["Hip Hop"],
    "release_date": "2017-06-30",
    "total_tracks": 10,
    "external_urls": {
      "external": "https://tidal.com/browse/album/251380836"
    },
    "source": "external",
    "links": {
      "self": "/api/v1/albums/tidal%3A251380836",
      "reviews": "/api/v1/albums/tidal%3A251380836/reviews",
      "open_source": "https://tidal.com/browse/album/251380836"
    },
    "actions": {
      "create_review": {
        "method": "POST",
        "href": "/api/v1/reviews",
        "requires_auth": true,
        "payload": {
          "album_id": "tidal:251380836"
        }
      },
      "ingest": {
        "method": "POST",
        "href": "/api/v1/albums/ingest",
        "requires_auth": true,
        "payload": {
          "external_id": "tidal:251380836"
        }
      }
    }
  },
  "tracks": [
    {
      "id": "track-1",
      "disc": 1,
      "track": 1,
      "name": "Kill Jay Z",
      "duration_ms": 205000
    }
  ],
  "stats": {
    "count": 2,
    "avg": 8.5
  }
}

GET /api/v1/albums/{id}/reviews

  • purpose: list reviews for a specific album
  • auth: none
  • path parameter: id string public album ID
  • returns:
    • data as review resources scoped to that album
    • collection links
    • collection actions
  • HATEOAS:
    • collection links include self, album, and reviews
    • collection actions include create_review
    • each review item includes links back to its own item route and related album routes

Example request:

GET /api/v1/albums/tidal%3A251380836/reviews
Accept: application/json

Example response:

{
  "data": [
    {
      "id": 12,
      "title": "A dense and rewarding record",
      "content": "This album becomes stronger on every listen and the production is excellent.",
      "rating": 9,
      "author": {
        "id": 4,
        "display_name": "Jake Andrews"
      },
      "created_at": "2026-04-02T11:40:21+00:00",
      "updated_at": "2026-04-02T11:40:21+00:00",
      "links": {
        "self": "/api/v1/reviews/12",
        "album": "/api/v1/albums/tidal%3A251380836",
        "album_reviews": "/api/v1/albums/tidal%3A251380836/reviews"
      }
    }
  ],
  "links": {
    "self": "/api/v1/albums/tidal%3A251380836/reviews",
    "album": "/api/v1/albums/tidal%3A251380836",
    "reviews": "/api/v1/reviews"
  },
  "actions": {
    "create_review": {
      "method": "POST",
      "href": "/api/v1/reviews",
      "requires_auth": true
    }
  }
}

GET /api/v1/reviews

  • purpose: list the latest reviews
  • auth: none
  • query parameters: limit from 1 to 100, default 50
  • returns:
    • data as review resources
    • collection links
    • collection actions
  • HATEOAS:
    • collection links include self, root, and create
    • collection actions include create_review

Example request:

GET /api/v1/reviews?limit=10
Accept: application/json

Example response:

{
  "data": [
    {
      "id": 12,
      "title": "A dense and rewarding record",
      "content": "This album becomes stronger on every listen and the production is excellent.",
      "rating": 9,
      "created_at": "2026-04-02T11:40:21+00:00",
      "updated_at": "2026-04-02T11:40:21+00:00",
      "author": {
        "id": 4,
        "display_name": "Jake Andrews"
      },
      "album": {
        "id": "tidal:251380836",
        "name": "4:44",
        "source": "external"
      },
      "links": {
        "self": "/api/v1/reviews/12",
        "album": "/api/v1/albums/tidal%3A251380836",
        "album_reviews": "/api/v1/albums/tidal%3A251380836/reviews"
      }
    }
  ],
  "links": {
    "self": "/api/v1/reviews?limit=10",
    "root": "/api/v1",
    "create": "/api/v1/reviews"
  },
  "actions": {
    "create_review": {
      "method": "POST",
      "href": "/api/v1/reviews",
      "requires_auth": true
    }
  }
}

GET /api/v1/reviews/{id}

  • purpose: fetch one review by numeric ID
  • auth: none
  • path parameter: id integer review ID
  • returns: one review resource in data
  • HATEOAS on the review resource:
    • links include self, album, and album_reviews
    • if the current user is allowed to change the review, action metadata can include update and delete

Example request:

GET /api/v1/reviews/12
Accept: application/json

Example response:

{
  "data": {
    "id": 12,
    "title": "A dense and rewarding record",
    "content": "This album becomes stronger on every listen and the production is excellent.",
    "rating": 9,
    "created_at": "2026-04-02T11:40:21+00:00",
    "updated_at": "2026-04-02T11:40:21+00:00",
    "author": {
      "id": 4,
      "display_name": "Jake Andrews"
    },
    "album": {
      "id": "tidal:251380836",
      "name": "4:44",
      "source": "external"
    },
    "links": {
      "self": "/api/v1/reviews/12",
      "album": "/api/v1/albums/tidal%3A251380836",
      "album_reviews": "/api/v1/albums/tidal%3A251380836/reviews"
    }
  }
}

POST /api/v1/reviews

  • purpose: create a new review for an existing album
  • auth: JWT required
  • request body:
    • album_id
    • title
    • content
    • rating
  • validation rules:
    • title required
    • content required and minimum 20 characters
    • rating must be between 1 and 10
    • album_id must resolve to a valid album with tracks
  • success response: 201 with the created review resource
  • main error responses:
    • 400 invalid JSON
    • 401 authentication required
    • 404 album not found
    • 422 validation failure

Example request:

POST /api/v1/reviews
Authorization: Bearer <jwt-token>
Content-Type: application/json
Accept: application/json

{
  "album_id": "tidal:251380836",
  "title": "A dense and rewarding record",
  "content": "This album becomes stronger on every listen and the production is excellent.",
  "rating": 9
}

Example response:

{
  "data": {
    "id": 12,
    "title": "A dense and rewarding record",
    "content": "This album becomes stronger on every listen and the production is excellent.",
    "rating": 9,
    "created_at": "2026-04-02T11:40:21+00:00",
    "updated_at": "2026-04-02T11:40:21+00:00",
    "author": {
      "id": 4,
      "display_name": "Jake Andrews"
    },
    "album": {
      "id": "tidal:251380836",
      "name": "4:44",
      "source": "external"
    },
    "links": {
      "self": "/api/v1/reviews/12",
      "album": "/api/v1/albums/tidal%3A251380836",
      "album_reviews": "/api/v1/albums/tidal%3A251380836/reviews"
    }
  }
}

PUT /api/v1/reviews/{id}

  • purpose: update an existing review
  • auth: JWT required
  • path parameter: id integer review ID
  • accepted body fields: title, content, rating
  • authorization: review author, moderator, or admin
  • success response: 200 with updated review resource
  • main error responses:
    • 400 invalid JSON
    • 401 missing/invalid JWT
    • 403 forbidden
    • 422 validation failure
  • implementation note: PUT currently shares the same update handler as PATCH

Example request:

PUT /api/v1/reviews/12
Authorization: Bearer <jwt-token>
Content-Type: application/json
Accept: application/json

{
  "title": "Revisited after a week",
  "content": "The writing is sharper than I first thought and the production has a lot more depth on repeat listens.",
  "rating": 8
}

Example response:

{
  "data": {
    "id": 12,
    "title": "Revisited after a week",
    "content": "The writing is sharper than I first thought and the production has a lot more depth on repeat listens.",
    "rating": 8,
    "created_at": "2026-04-02T11:40:21+00:00",
    "updated_at": "2026-04-02T12:05:44+00:00",
    "author": {
      "id": 4,
      "display_name": "Jake Andrews"
    },
    "album": {
      "id": "tidal:251380836",
      "name": "4:44",
      "source": "external"
    },
    "links": {
      "self": "/api/v1/reviews/12",
      "album": "/api/v1/albums/tidal%3A251380836",
      "album_reviews": "/api/v1/albums/tidal%3A251380836/reviews"
    }
  }
}

PATCH /api/v1/reviews/{id}

  • purpose: partially update an existing review
  • auth: JWT required
  • path parameter: id integer review ID
  • accepted body fields: title, content, rating
  • authorization: review author, moderator, or admin
  • success response: 200 with updated review resource
  • main error responses:
    • 400 invalid JSON
    • 401 missing/invalid JWT
    • 403 forbidden
    • 422 validation failure

Example request:

PATCH /api/v1/reviews/12
Authorization: Bearer <jwt-token>
Content-Type: application/json
Accept: application/json

{
  "rating": 10
}

Example response:

{
  "data": {
    "id": 12,
    "title": "A dense and rewarding record",
    "content": "This album becomes stronger on every listen and the production is excellent.",
    "rating": 10,
    "created_at": "2026-04-02T11:40:21+00:00",
    "updated_at": "2026-04-02T12:10:03+00:00",
    "author": {
      "id": 4,
      "display_name": "Jake Andrews"
    },
    "album": {
      "id": "tidal:251380836",
      "name": "4:44",
      "source": "external"
    },
    "links": {
      "self": "/api/v1/reviews/12",
      "album": "/api/v1/albums/tidal%3A251380836",
      "album_reviews": "/api/v1/albums/tidal%3A251380836/reviews"
    }
  }
}

DELETE /api/v1/reviews/{id}

  • purpose: delete a review
  • auth: JWT required
  • path parameter: id integer review ID
  • authorization: review author, moderator, or admin
  • success response: 204 No Content
  • main error responses:
    • 401 missing/invalid JWT
    • 403 forbidden
    • 404 not found

Example request:

DELETE /api/v1/reviews/12
Authorization: Bearer <jwt-token>
Accept: application/json

Example response:

204 No Content

Architecture Summary

The project follows standard Symfony separation of concerns.

  • controllers handle HTTP orchestration only
  • services contain business logic and external API integration
  • repositories handle domain-specific queries
  • entities define persisted state
  • forms handle server-side validation for web submissions
  • security config and voters enforce route access and ownership rules
  • event subscribers normalize API errors into JSON responses

This matters for the brief because the API is not implemented as one large controller. It is structured in a maintainable way using framework conventions and reusable services.

API Implementation Summary

The API implementation is centered around resource-based routes rather than action-style routes. Review endpoints expose collection and item resources, while album endpoints expose search, detail, ingestion, and album-specific review resources.

Key implementation points:

  • uses descriptive REST-style URIs such as /api/v1/reviews and /api/v1/albums/{id}/reviews
  • uses correct HTTP verbs for retrieval, creation, update, and deletion
  • returns JSON for both success and failure cases
  • includes HATEOAS-style hypermedia in API responses through links and actions
  • uses JWT authentication for protected write operations
  • excludes sensitive data such as user email and password hashes from API responses
  • uses structured status codes including 200, 201, 204, 401, 403, 404, and 422
  • provides generated OpenAPI documentation through Nelmio

The review API supports full CRUD behaviour for third-party clients. Public reads are available without authentication, while create, update, and delete require authenticated users. Authorization is more specific than simple login checks because review ownership is enforced with a ReviewVoter, allowing moderators and admins to manage any review while normal users can only manage their own.

The API also includes a useful sub-resource:

  • GET /api/v1/albums/{id}/reviews

This improves the resource model because reviews can be queried globally or in the context of a specific album.

The API also now includes a lightweight HATEOAS layer. The base API index exposes navigational links such as self, albums, reviews, and docs, plus action metadata for login. Album and review resources expose their own links and, where relevant, actions, for example:

  • album resources link to self, reviews, and open_source
  • album resources expose actions such as create_review and ingest
  • review resources link back to their own item route and related album routes
  • review collections expose a create_review action for discoverability

This improves the API from a client perspective because consumers do not need to hard-code every related route manually. Instead, the response itself advertises relevant next operations.

API Consumption Summary

The application consumes external music APIs through Guzzle and a provider abstraction rather than binding the UI directly to one upstream service.

Implemented external providers:

  • Last.fm
  • MusicBrainz
  • TIDAL

Provider logic is normalized behind ExternalAlbumClientInterface, with concrete clients for each provider. This means the rest of the application receives one consistent album payload structure regardless of the source.

The consumed API data is used to:

  • search external album catalogs
  • populate album metadata
  • fetch artists, genres, release dates, and track lists
  • fetch cover art
  • ingest external albums into the local database
  • enrich album detail pages and search results in the UI

This is deep integration rather than a token demo call. External data directly affects the search experience, album display, save/ingest flow, and review context.

External Integration Details

Last.fm

Used for album search and metadata enrichment. Responses are cached and normalized into the project’s album format.

MusicBrainz

Used as an alternative provider and for structured release metadata. The project supports optional authenticated requests and cover-art URL generation via normalized output.

TIDAL

Used as another full external provider. The TidalClient implements OAuth 2.0 client credentials, caches access tokens, retries on expired auth, and converts JSON:API responses into the same normalized album contract used elsewhere.

This strengthens the project against the brief because it goes beyond a single trivial API call and includes a second non-trivial external integration path.

Security Summary

Security is handled at multiple layers:

  • JWT authentication for protected API routes
  • Symfony access control rules for route-level protection
  • role hierarchy: admin > moderator > user
  • ReviewVoter for ownership-based edit/delete permissions
  • normalized JSON authentication failures for API clients
  • exclusion of sensitive user fields from serialized API responses

This is stronger than basic coursework authentication because it combines framework security with resource-level authorization.

Error Handling and Robustness

API error handling is centralized so clients receive JSON errors rather than HTML exception pages.

The project includes:

  • a shared API response helper
  • exception subscribers that normalize failures
  • JWT failure subscribers for missing, invalid, or expired tokens
  • validation logic for malformed JSON and invalid field values
  • server-side handling for missing albums, forbidden edits, and unsupported methods

This improves client usability and supports a more production-like API design.

Testing Summary

The project includes both functional and unit tests.

Functional API tests cover:

  • missing JWT
  • invalid JWT
  • malformed JSON
  • validation failures
  • forbidden review updates
  • missing album resources
  • protection of sensitive author data
  • hypermedia links and action metadata on API index, album, and album-review responses

Unit tests cover provider-specific service behaviour, including TIDAL payload normalization and provider routing.

This supports the brief’s emphasis on working, demonstrable features rather than partially implemented code.

How The Project Meets The Brief

API Implementation

The brief requires a usable RESTful API capable of listing, reading, creating, editing, and deleting album reviews in JSON. This project meets that with:

  • GET, POST, PUT, PATCH, and DELETE review endpoints
  • JWT-protected write routes
  • appropriate resource names
  • JSON request and response formats
  • HATEOAS-style links and actions to improve discoverability
  • status-code-based error handling
  • sub-resource support through album review listing
  • automatically generated OpenAPI documentation

API Consumption

The brief requires at least two significantly different examples of API interaction and integration into the review website. This project meets that with:

  • external album search
  • album metadata retrieval
  • track list retrieval
  • cover art integration
  • album ingestion into local storage
  • multiple provider implementations

The consumed data is visible in the website itself, especially on search results and album detail pages.

Documentation and Report Potential

The project already has strong source material for the report because it includes:

  • generated OpenAPI docs
  • explicit controller routes and OpenAPI attributes
  • centralized security configuration
  • reusable external service classes
  • tests that demonstrate expected API behaviour

Honest Technical Caveats

  • PUT and PATCH currently share the same update action, so PUT behaves more like a flexible update than a strict full replacement
  • local verification in this shell was limited because php is not installed here, so PHPUnit was not run live during this session
  • some observability headers in album search are still named with Lastfm even though the provider layer now supports multiple providers

These are not major blockers, but they are worth understanding if you want the final report to sound precise and credible.

Strongest Points To Emphasize In A Final Report

  • the API is versioned and resource-oriented
  • the API now includes lightweight HATEOAS support through response links and actions
  • write operations are protected with JWT rather than session auth
  • review ownership is enforced through a voter, not just a login check
  • external APIs are abstracted behind a provider interface
  • external data is deeply integrated into the UI and persisted catalog
  • OpenAPI documentation is generated automatically
  • the project includes automated tests for important API behaviour

Short Project Summary

Tonehaus is a Symfony-based music review platform extended into a RESTful web application. It implements a versioned JSON API for review CRUD and album access, secures write routes with JWT, consumes multiple external music providers through Guzzle and a normalized service layer, and integrates that data directly into album discovery, metadata display, tracklists, and review workflows. The architecture is modular, test-backed, and aligned with the main goals of the Assignment 2 brief.