Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
81 commits
Select commit Hold shift + click to select a range
53aa845
MVCS to hexagonal architecture : Create domain layer with business en…
raynaldlao Mar 24, 2026
65dd17c
MVCS to hexagonal architecture : Introduce account data access port a…
raynaldlao Mar 24, 2026
af5ea63
MVCS to hexagonal architecture : Implement account creation logic and…
raynaldlao Mar 25, 2026
7f281f0
MVCS to hexagonal architecture : Implement article creation and retri…
raynaldlao Mar 25, 2026
820cd06
MVCS to hexagonal architecture : Improve article creation by enforcin…
raynaldlao Mar 26, 2026
c8519d3
MVCS to hexagonal architecture : Refactored ownership checks with rol…
raynaldlao Mar 27, 2026
e4ca5fe
MVCS to hexagonal architecture : Add TODO markers for future exceptions
raynaldlao Mar 27, 2026
d3aa82b
MVCS to hexagonal architecture : Update unit tests to use `fake_accou…
raynaldlao Mar 27, 2026
e40632f
MVCS to hexagonal architecture : Implemented delete_article with auth…
raynaldlao Mar 27, 2026
762d2af
MVCS to hexagonal architecture : Add article pagination and total-cou…
raynaldlao Mar 28, 2026
0a339fa
MVCS to hexagonal architecture : Delete unnecessary line breaks to im…
raynaldlao Mar 28, 2026
bfa7362
MVCS to hexagonal architecture : Standardize error handling by return…
raynaldlao Mar 30, 2026
00d4040
MVCS to hexagonal architecture : Adds the comment‑creation use case b…
raynaldlao Mar 30, 2026
5014039
MVCS to hexagonal architecture : This refactor simplifies the applica…
raynaldlao Mar 30, 2026
e341734
MVCS to hexagonal architecture : Implements the reply‑to‑comment feat…
raynaldlao Mar 30, 2026
aeaab39
MVCS to hexagonal architecture : Implement get_comments_for_article w…
raynaldlao Mar 30, 2026
b1746ed
MVCS to hexagonal architecture : Introducing strict admin‑only contro…
raynaldlao Mar 31, 2026
b4c40b4
MVCS to hexagonal architecture : Replacing hard‑coded role strings wi…
raynaldlao Mar 31, 2026
d88e9d1
MVCS to hexagonal architecture : Grouped the 18 ArticleService tests …
raynaldlao Mar 31, 2026
9d0301a
MVCS to hexagonal architecture : Grouped the 13 CommentService tests …
raynaldlao Mar 31, 2026
d169348
MVCS to hexagonal architecture : Reorganize login and registration t…
raynaldlao Mar 31, 2026
55e81df
MVCS to hexagonal architecture : Restored autospec=True on all Repos…
raynaldlao Apr 1, 2026
48f1af9
MVCS to hexagonal architecture : Increased Ruff’s line-length to 130 …
raynaldlao Apr 1, 2026
35dfcf3
MVCS to hexagonal architecture : Added a save() call in ArticleServic…
raynaldlao Apr 1, 2026
8a199cb
MVCS to hexagonal architecture : Reorganized the factory methods by e…
raynaldlao Apr 1, 2026
19c5676
MVCS to hexagonal architecture : Adds `pydantic[email]` as a dependen…
raynaldlao Apr 2, 2026
8491ecb
MVCS to hexagonal architecture : Introduces a dedicated hexagonal inf…
raynaldlao Apr 2, 2026
5cf45d2
MVCS to hexagonal architecture : The infrastructure tests were restru…
raynaldlao Apr 2, 2026
0ae38f6
MVCS to hexagonal architecture : Adds the missing infrastructure for …
raynaldlao Apr 3, 2026
4fa62d3
MVCS to hexagonal architecture : Centralizes SQL test data setup by m…
raynaldlao Apr 4, 2026
2434657
MVCS to hexagonal architecture : Renames adapter test classes with cl…
raynaldlao Apr 4, 2026
36612d5
MVCS to hexagonal architecture : Merges login and registration logic …
raynaldlao Apr 4, 2026
7ae9f5c
MVCS to hexagonal architecture : Adds missing documentation across th…
raynaldlao Apr 4, 2026
885ace4
MVCS to hexagonal architecture : Defines `AccountManagementPort` and …
raynaldlao Apr 5, 2026
442c8a3
MVCS to hexagonal architecture : Adds `ArticleManagementPort` as the …
raynaldlao Apr 5, 2026
a2627da
MVCS to hexagonal architecture : Adds `CommentManagementPort` as the …
raynaldlao Apr 5, 2026
70022a0
MVCS to hexagonal architecture : Adds account input DTOs (`LoginReque…
raynaldlao Apr 5, 2026
caf2ced
MVCS to hexagonal architecture : Adds `AccountResponse` DTO to safely…
raynaldlao Apr 5, 2026
0e21dfa
MVCS to hexagonal architecture : Splits account management into dedic…
raynaldlao Apr 6, 2026
640be56
MVCS to hexagonal architecture : Implements hexagonal account session…
raynaldlao Apr 6, 2026
05104a9
MVCS to hexagonal architecture : Centralizes and renames account sess…
raynaldlao Apr 7, 2026
926d3d1
MVCS to hexagonal architecture : Implements hexagonal input adapters …
raynaldlao Apr 7, 2026
0a39f77
MVCS to hexagonal architecture : Improves the session output adapter …
raynaldlao Apr 7, 2026
59075c3
MVCS to hexagonal architecture : Moves the SQLAlchemy tests into a de…
raynaldlao Apr 7, 2026
1fccd1c
MVCS to hexagonal architecture : Refactors test data generation by un…
raynaldlao Apr 8, 2026
8d9971c
MVCS to hexagonal architecture : Simplifies input adapter tests by ex…
raynaldlao Apr 8, 2026
c8fcd14
MVCS to hexagonal architecture : Adds lightweight integration for the…
raynaldlao Apr 8, 2026
7d2dfdd
MVCS to hexagonal architecture : Extracts inline CSS into static file…
raynaldlao Apr 8, 2026
3d16aaa
MVCS to hexagonal architecture : Implements the full hexagonal Articl…
raynaldlao Apr 8, 2026
19f090a
MVCS to hexagonal architecture : Remove boilerplate context blocks in…
raynaldlao Apr 8, 2026
3c5af9a
MVCS to hexagonal architecture : Restructures the infrastructure laye…
raynaldlao Apr 8, 2026
df9ba29
MVCS to hexagonal architecture : Adds a central FlaskHandler for sess…
raynaldlao Apr 9, 2026
40df012
MVCS to hexagonal architecture : Synchronizes ArticleRequest with the…
raynaldlao Apr 9, 2026
f0d6905
MVCS to hexagonal architecture : Unifies TestFlaskHandler with the sh…
raynaldlao Apr 9, 2026
48c46b9
MVCS to hexagonal architecture : Adds missing Not‑Found cases for art…
raynaldlao Apr 9, 2026
0d8d681
MVCS to hexagonal architecture : Improve dedicated tests for LoginReq…
raynaldlao Apr 9, 2026
37a3abb
MVCS to hexagonal architecture : Centralizes engine and schema setup …
raynaldlao Apr 9, 2026
08a076e
MVCS to hexagonal architecture : Adds the comment bootstrap and depen…
raynaldlao Apr 10, 2026
c8a0cdb
MVCS to hexagonal architecture : Fixes LoginService by injecting the …
raynaldlao Apr 10, 2026
ff84179
MVCS to hexagonal architecture : Restores the legacy minimalist layou…
raynaldlao Apr 10, 2026
6873ff9
MVCS to hexagonal architecture : Fixes ID sync in SQLAlchemy adapters…
raynaldlao Apr 12, 2026
a0c2187
MVCS to hexagonal architecture : Adds missing email and confirm_passw…
raynaldlao Apr 12, 2026
bd60b09
MVCS to hexagonal architecture : Adds architectural tests enforcing d…
raynaldlao Apr 13, 2026
33b7811
MVCS to hexagonal architecture : Implements batch author fetching to …
raynaldlao Apr 15, 2026
9c31d13
MVCS to hexagonal architecture : Consolidates tests into four themati…
raynaldlao Apr 15, 2026
926a77f
MVCS to hexagonal architecture : Adds exhaustive validation tests for…
raynaldlao Apr 16, 2026
f34c9c1
MVCS to hexagonal architecture : Fixes long lines and trailing whites…
raynaldlao Apr 16, 2026
b3a8f88
MVCS to hexagonal architecture : Extracts core and web bootstrap logi…
raynaldlao Apr 16, 2026
b18e85c
MVCS to hexagonal architecture : Introduces dedicated exception hiera…
raynaldlao Apr 16, 2026
5275a2e
MVCS to hexagonal architecture : Introduces in‑memory repositories fo…
raynaldlao Apr 17, 2026
bfc1156
MVCS to hexagonal architecture : Migrates mocks to InMemory adapters …
raynaldlao Apr 17, 2026
b28699b
MVCS to hexagonal architecture : Replaces suppression comments with p…
raynaldlao Apr 17, 2026
1c9b9c3
MVCS to hexagonal architecture : Updates the test execution step to r…
raynaldlao Apr 17, 2026
a4141d5
MVCS to hexagonal architecture : Ensures Flask can locate the templat…
raynaldlao Apr 17, 2026
f9c4d23
MVCS to hexagonal architecture : Reduces SQL calls from 5 to 3 for th…
raynaldlao Apr 22, 2026
bd29ab1
MVCS to hexagonal architecture : Enhances FlaskSessionAdapter resilie…
raynaldlao Apr 22, 2026
d9bff01
MVCS to hexagonal architecture : Adds an integration test for Secret …
raynaldlao Apr 22, 2026
fdda763
MVCS to hexagonal architecture : Standardizes role checks to lowercas…
raynaldlao Apr 23, 2026
a6c2a99
MVCS to hexagonal architecture : Remove unnecessary comments
raynaldlao Apr 23, 2026
231753c
MVCS to hexagonal architecture : Update outdated dependencies (black,…
raynaldlao Apr 23, 2026
661318d
MVCS to hexagonal architecture : Add a missing docstring and adjust l…
raynaldlao Apr 23, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/continuous-integration.yml
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ jobs:
migrate

- name: Run tests
run: poetry run pytest
run: poetry run pytest tests_hexagonal/

- name: Run type check
run: poetry run pyright
Expand Down
197 changes: 197 additions & 0 deletions blog_comment_application.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
import os

from flask import Flask
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker

from src.application.services.article_service import ArticleService
from src.application.services.comment_service import CommentService
from src.application.services.login_service import LoginService
from src.application.services.registration_service import RegistrationService
from src.infrastructure.config import infra_config
from src.infrastructure.input_adapters.flask.flask_account_session_adapter import AccountSessionAdapter
from src.infrastructure.input_adapters.flask.flask_article_adapter import ArticleAdapter
from src.infrastructure.input_adapters.flask.flask_comment_adapter import CommentAdapter
from src.infrastructure.input_adapters.flask.flask_login_adapter import LoginAdapter
from src.infrastructure.input_adapters.flask.flask_registration_adapter import RegistrationAdapter
from src.infrastructure.output_adapters.session.flask_session_adapter import FlaskSessionAdapter
from src.infrastructure.output_adapters.sqlalchemy.sqlalchemy_account_adapter import SqlAlchemyAccountAdapter
from src.infrastructure.output_adapters.sqlalchemy.sqlalchemy_article_adapter import SqlAlchemyArticleAdapter
from src.infrastructure.output_adapters.sqlalchemy.sqlalchemy_comment_adapter import SqlAlchemyCommentAdapter


def _setup_database(db_session=None):
"""
Initializes the database connection and session.

Args:
db_session: Optional existing SQLAlchemy session (e.g., for testing).

Returns:
Session: A configured SQLAlchemy database session.
"""
if db_session is None:
engine = create_engine(infra_config.database_url)
session_factory = sessionmaker(bind=engine)
db_session = session_factory()
return db_session


def _create_output_adapters(db_session):
"""
Instantiates the persistence adapters (repositories).

Args:
db_session: The SQLAlchemy session to be injected into adapters.

Returns:
dict: A dictionary containing initialized repository adapters.
"""
account_repo = SqlAlchemyAccountAdapter(db_session)
return {
"account_repo": account_repo,
"article_repo": SqlAlchemyArticleAdapter(db_session),
"comment_repo": SqlAlchemyCommentAdapter(db_session),
"session_repo": FlaskSessionAdapter(account_repo),
}


def _create_services(repositories):
"""
Instantiates the core application services.

Args:
repositories (dict): A dictionary of initialized repositories.

Returns:
dict: A dictionary containing initialized core services.
"""
registration_service = RegistrationService(repositories["account_repo"])
session_repo = repositories["session_repo"]
account_repo = repositories["account_repo"]
article_repo = repositories["article_repo"]
comment_repo = repositories["comment_repo"]

login_service = LoginService(account_repo, session_repo)
comment_service = CommentService(comment_repo, article_repo, account_repo)
article_service = ArticleService(article_repo, account_repo, comment_repo)

return {
"registration_service": registration_service,
"session_repo": session_repo,
"login_service": login_service,
"comment_service": comment_service,
"article_service": article_service,
}


def _init_web_facade_flask():
"""
Initializes the Flask application instance (Web Facade).

Returns:
Flask: The initialized Flask application object.
"""
base_dir = os.path.dirname(os.path.abspath(__file__))
template_dir = os.path.join(base_dir, "src/infrastructure/input_adapters/templates")
static_dir = os.path.join(base_dir, "src/infrastructure/input_adapters/static")
app = Flask(__name__, template_folder=template_dir, static_folder=static_dir)
app.secret_key = os.getenv("SECRET_KEY", "dev_secret_key")
app.config["SESSION_COOKIE_SAMESITE"] = "Lax"
return app


def _init_web_adapters(services):
"""
Instantiates the input adapters for the Web interface.

Args:
services (dict): A dictionary of initialized core services.

Returns:
dict: A dictionary containing initialized Flask adapters.
"""
return {
"article_adapter": ArticleAdapter(services["article_service"]),
"comment_adapter": CommentAdapter(services["comment_service"]),
"login_adapter": LoginAdapter(services["login_service"]),
"registration_adapter": RegistrationAdapter(services["registration_service"]),
"account_session_adapter": AccountSessionAdapter(services["login_service"]),
}


def _register_web_routes(app, adapters):
"""
Registers the URL rules for the Web interface facade.

Args:
app (Flask): The Flask application instance.
adapters (dict): A dictionary of initialized Web adapters.
"""
art = adapters["article_adapter"]
app.add_url_rule("/", view_func=art.list_articles, endpoint="article.list_articles")
app.add_url_rule("/articles/<int:article_id>", view_func=art.read_article, endpoint="article.read_article")
app.add_url_rule("/articles/new", view_func=art.render_create_page, methods=["GET"], endpoint="article.render_create_page")
app.add_url_rule("/articles/new", view_func=art.create_article, methods=["POST"], endpoint="article.create_article")
app.add_url_rule(
"/articles/<int:article_id>/edit", view_func=art.render_edit_page, methods=["GET"], endpoint="article.render_edit_page"
)
app.add_url_rule(
"/articles/<int:article_id>/edit", view_func=art.update_article, methods=["POST"], endpoint="article.update_article"
)
app.add_url_rule(
"/articles/<int:article_id>/delete", view_func=art.delete_article, methods=["POST"], endpoint="article.delete_article"
)

com = adapters["comment_adapter"]
app.add_url_rule(
"/articles/<int:article_id>/comments", view_func=com.create_comment, methods=["POST"], endpoint="comment.create_comment"
)
app.add_url_rule(
"/articles/<int:article_id>/comments/<int:parent_comment_id>/reply",
view_func=com.reply_to_comment,
methods=["POST"],
endpoint="comment.reply_to_comment",
)
app.add_url_rule(
"/articles/<int:article_id>/comments/<int:comment_id>/delete",
view_func=com.delete_comment,
methods=["POST"],
endpoint="comment.delete_comment",
)

log = adapters["login_adapter"]
reg = adapters["registration_adapter"]
acc = adapters["account_session_adapter"]
app.add_url_rule("/login", view_func=log.render_login_page, methods=["GET"], endpoint="auth.login")
app.add_url_rule("/login", view_func=log.authenticate, methods=["POST"], endpoint="auth.authenticate")
app.add_url_rule("/register", view_func=reg.render_registration_page, methods=["GET"], endpoint="registration.register")
app.add_url_rule("/register", view_func=reg.register, methods=["POST"], endpoint="registration.register_action")
app.add_url_rule("/profile", view_func=acc.display_profile, endpoint="auth.profile")
app.add_url_rule("/logout", view_func=acc.logout, endpoint="auth.logout")


def create_app(db_session=None) -> Flask:
"""
Bootstrap function to initialize the hexagonal application.
Orchestrates the assembly of the Core and the Web Facade.

Args:
db_session: Optional pre-existing database session.

Returns:
Flask: The configured Flask application (Web Facade).
"""
db_session = _setup_database(db_session)
repositories = _create_output_adapters(db_session)
services = _create_services(repositories)
app = _init_web_facade_flask()
web_adapters = _init_web_adapters(services)
_register_web_routes(app, web_adapters)
web_adapters["account_session_adapter"].register_before_request_handler(app)
return app


if __name__ == "__main__":
application = create_app()
application.run(debug=True)
Loading
Loading