diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index bf9b106..3bdded7 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -307,29 +307,27 @@ The main advantages in my opinion are: `client` (adapter for an external service) - `extension` refers to the file extension such as `ts` -> [!TIP] -> **FRONTEND** naming convention: -> -> If the file's default export is a **React component** — use **[PascalCase](http://c2.com/cgi/wiki?PascalCase)**. -> For everything else — use **[camelCase](https://wiki.c2.com/?CamelCase)**. - - + > [!NOTE] -> **What are `PascalCase` and `camelCase`?** +> **What are `PascalCase`, `snake_case`, and `camelCase`?** > > - **[PascalCase](http://c2.com/cgi/wiki?PascalCase)** is a naming convention where the first letter of every word - > is capitalized, with no spaces or underscores between words: `YouTubeEmbed`. -> - **[camelCase](https://wiki.c2.com/?CamelCase)** is a naming convention where the first word starts with a lowercase - > letter and each subsequent word begins with an uppercase letter, with no spaces or underscores: `useJuryVote`. + > is capitalized, with no spaces or underscores between words such as `YouTubeEmbed`. +> - **[snake_case](https://en.wikipedia.org/wiki/Snake_case)** +> is a naming convention where words are lowercase and separated +> with underscores (`_`), such as `first_name`. +> - **[camelCase](https://wiki.c2.com/?CamelCase)** is a naming convention +> where the first word starts with a lowercase letter and each subsequent +> word begins with an uppercase letter, with no spaces or underscores +> such as `useJuryVote`. -#### Database Schema +#### Database -This section describes how the database is structured. -The *learn-dev* platform uses a PostgreSQL relational database to persist entities. +The *learn-dev* platform uses a **PostgreSQL** relational database to persist entities. -##### Database Naming Conventions +#### Database Naming Conventions Here are the naming conventions for the **name** of our database **tables**: @@ -341,10 +339,72 @@ Here are the naming conventions for the **name** of our database **tables**: Do not use an underscore as the first character. -##### Database ERD Diagram +#### Database Schema + +This section describes the data model using the progressive 3 diagrams +(MCD, MLD, and MPD) from the Merise methodology. +This gives a view from high-level conceptual model (MCD), logical model (MLD) to +physical model (MPD) with all the database details. + + +#### MCD Diagram + +*MCD* stands for 🇫🇷 **Modèle Conceptuel de Données** (Conceptual Data Model). +The *MCD diagram* is part of the *Merise* methodology and shows the entities +and relationships without the (database) technical details. + +It is a high-level **business-domain** oriented diagram +that shows the **data (entities)**, their **relationships** and **cardinalities**, +with **NO technical and implementation details**. + +**Learn-dev MCD Diagram**: + +> ![MCD](docs/database/mcd/learn-dev.svg) + + +#### MLD Diagram + +*MLD* stands for 🇫🇷 **Modèle Logique de Données** (Logical Data Model). +The **MLD diagram** is part of the _Merise_ methodology and shows +the *Logical Data Model*. + +It shows the relational structure in a database-agnostic way. +It is a transformed version of the MCD where: +- entities become tables, +- `1..N` relationships become foreign keys, +- `N..N` relationships become junction tables, +- `1..1` relationships become foreign keys. + +The *MLD* is shared with domain experts and application developers. +*Domain experts* can verify that the relational structure accurately reflects +the business. +*Application developers* can then start creating the entities. + +**Learn-dev MLD Diagram**: + +> ![MLD](docs/database/mld/learn-dev_mld.svg) + + +#### MPD Diagram + +*MPD* stands for 🇫🇷*Modèle Physique des Données* (Physical Data Model). + +This diagram is exhaustive and database-specific. +It contains all the tables, fields, keys, +database-specific data types, and constraints... +It is aimed at database administrators and application developers. +It can be used to implement the data model in the database. +Database administrators use it to create the migration scripts. + +**Learn-dev MPD Diagram**: + +> TODO: ![MPD](docs/database/mpd/learn-dev_mpd.svg) + + +#### ERD Diagram + +> TODO: ![ERD Diagram](docs/database/mpd/ERD.svg) -The **Entity Relationships Diagram** (ERD) -is available as an [SVG image](https://raw.githubusercontent.com/ebouchut/learn-dev/dev/docs/ERD.svg) > [!NOTE] > This diagram uses [Crow's foot notation](https://mermaid.js.org/syntax/entityRelationshipDiagram.html#relationship-syntax) diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..41fdf99 --- /dev/null +++ b/Makefile @@ -0,0 +1,61 @@ +# Ignore existing files with the same name as phony targets +.PHONY: help diagrams mcd mld mpd ddl clean + +# Default make target used if none specified +.DEFAULT_GOAL := help + +# Display the syntax with available targets +help: + @echo "Available targets:" + @echo " make diagrams — generate MCD, MLD, and MPD" + @echo " make mcd — generate MCD" + @echo " make mld — generate MLD" + @echo " make mpd — generate MPD" + @echo " make ddl — regenerate DDL (SQL with Postgres database structure)" + @echo " make clean — remove generated diagrams" + +# Generate all database diagrams (MCD, MLD, MPD) +diagrams: mcd mld mpd + @echo "All diagrams generated (MCD, MLD, MPD)" + +# Generate MCD from Mocodo source +mcd: + @echo "Generating MCD..." + mocodo --input docs/database/mcd/learn-dev.mcd --output_dir docs/database/mcd --colors brewer+1 + @echo "MCD generated in docs/database/mcd/" + +# Generate MLD (2 step process): +# - transform MCD source into a MLD source: docs/database/mld/learn-dev_mld.mcd +# - Render the MLD source +mld: + @echo "Generating MLD..." + mocodo --input docs/database/mcd/learn-dev.mcd --output_dir docs/database/mld --transform mld diagram + rm -f docs/database/mld/learn-dev.* + mocodo --input docs/database/mld/learn-dev_mld.mcd --output_dir docs/database/mld --colors ocean + @echo "MLD generated in docs/database/mld/learn-dev_mld.svg" + +# Generate MPD from the PostgreSQL database +mpd: + @echo "Generating MPD from PostgreSQL Database..." + @mkdir -p docs/database/mpd + @test -n "$(TBLS_DSN)" || (echo "TBLS_DSN is required (e.g., postgres://user:pass@host:5432/dbname)" >&2; exit 1) + tbls doc "$(TBLS_DSN)" docs/database/mpd --force + @echo "MPD generated in docs/database/mpd/" + +# Generate a SQL file to create the database structure (tables, associations) +# (with Postgres DDL syntax) +ddl: + @echo "Generating DDL (Postgres SQL syntax)..." + @mkdir -p docs/database/ddl + mocodo --input docs/database/mcd/learn-dev.mcd --output_dir docs/database/ddl -t postgres + @echo "DDL generated in docs/database/ddl/" + +# Clean up generated diagram files +clean: + @echo "Cleaning up generated diagrams..." + rm -f docs/database/mcd/learn-dev.svg + rm -f docs/database/mcd/learn-dev_geo.json + rm -f docs/database/mld/* + rm -f docs/database/mpd/* + rm -f docs/database/ddl/* + @echo "Cleaned" diff --git a/README.md b/README.md index 070824a..978530a 100644 --- a/README.md +++ b/README.md @@ -93,6 +93,45 @@ Install _Docker_ and _Docker Compose_: ``` - on [Windows and Linux](https://docs.docker.com/get-started/get-docker/) +### Python Setup + +This is **optional** if you only need **to run the application**. + +This is necessary in order to regenerate the MERISE database diagrams +(MCD, MLD and MPD) after any changes have been made to the database design. + +You will need to install *Python* and: +- **`mocodo`**: a CLI tool to generate the MCD and MLD database diagrams + from a text-file description of the conceptual data model. +- **`tbls`**: a CLI tool to reverse engineer the live database to generate the MPD. + +Here is the procedure: + +- [ ] Install Python + - Install Python on macOS + ```shell + brew install python@3.14 + ``` + - Install Python on other OSes: + https://docs.python-guide.org/en/latest/starting/installation/ +- [ ] Create a Python Virtual Environment: + ```shell + # cd to the folder where you cloned the repository + python3 -m venv venv # Do it once + source venv/bin/activate # Run this line each time you open a new shell/terminal/window/tab + ``` +- [ ] Install **[mocodo](https://laowantong.github.io/mocodo/doc/fr_refman.html#Installation-et-lancement-du-programme)** + ```shell + pip install 'mocodo[svg,clipboard]' + ``` +- [ ] Install **[tbls](https://github.com/k1LoW/tbls#install)** + ```shell + # On macOS + brew install tbls + + # or any OS with Go installed + go install github.com/k1LoW/tbls@latest + ``` ## Configuration diff --git a/docs/database/mcd/learn-dev.mcd b/docs/database/mcd/learn-dev.mcd new file mode 100644 index 0000000..1948f43 --- /dev/null +++ b/docs/database/mcd/learn-dev.mcd @@ -0,0 +1,28 @@ +% learn-dev MCD (Modele Conceptuel des Données). +% This text file uses Mocodo-syntax to describe the MCD. +% +% See: +% - GitHub Repository: https://github.com/laowantong/mocodo +% - Syntax: https://laowantong.github.io/mocodo/doc/fr_refman.html#Syntaxe-de-description-d'un-MCD +% Usage: +% mocodo --input docs/database/mcd/learn-dev.mcd --output_dir docs/database/mcd --colors brewer+1 + +: +Email Token: token_id, token, expires_at, used_at +Audit Log: log_id, action_type, entity_type, entity_id, description, ip_address, user_agent, was_successful, error_message, metadata +: + +Reset Token: token_id, token, expires_at, used_at, ip_address +Receives, 11 Email Token, 0N User +Performs, 01 Audit Log, 0N User +: + +Requests, 11 Reset Token, 0N User +User: user_id, username, email, password, first_name, last_name, is_active, is_verified, is_locked, failed_login_attempts, last_login_at, password_changed_at +Holds, 0N User, 0N Role : assigned_at, assigned_by +Role: role_id, role_name, description, is_active + +: +Owns, 11 Refresh Token, 0N User +Refresh Token: token_id, token, expires_at, revoked_at, revoked_reason, ip_address, user_agent, last_used_at +: \ No newline at end of file diff --git a/docs/database/mcd/learn-dev.svg b/docs/database/mcd/learn-dev.svg new file mode 100644 index 0000000..066f1fb --- /dev/null +++ b/docs/database/mcd/learn-dev.svg @@ -0,0 +1,197 @@ + + + + + + + + + + + + + + + Receives + + 1,1 + 0,N + + + + + + + + + + + + Performs + + 0,1 + 0,N + + + + + + + + + + + + Requests + + 1,1 + 0,N + + + + + + + + + + + + Holds + assigned_at + assigned_by + + 0,N + 0,N + + + + + + + + + + + + Owns + + 1,1 + 0,N + + + + + + + + + + + Email Token + token_id + + token + expires_at + used_at + + + + + + + + + + + Audit Log + log_id + + action_type + entity_type + entity_id + description + ip_address + user_agent + was_successful + error_message + metadata + + + + + + + + + + + Reset Token + token_id + + token + expires_at + used_at + ip_address + + + + + + + + + + + User + user_id + + username + email + password + first_name + last_name + is_active + is_verified + is_locked + failed_login_attempts + last_login_at + password_changed_at + + + + + + + + + + + Role + role_id + + role_name + description + is_active + + + + + + + + + + + Refresh Token + token_id + + token + expires_at + revoked_at + revoked_reason + ip_address + user_agent + last_used_at + + \ No newline at end of file diff --git a/docs/database/mcd/learn-dev_geo.json b/docs/database/mcd/learn-dev_geo.json new file mode 100644 index 0000000..7d256b0 --- /dev/null +++ b/docs/database/mcd/learn-dev_geo.json @@ -0,0 +1,43 @@ +{ + "width": 551, + "height": 819, + "cx": [ + [ "EMAIL_TOKEN", 230 ], + [ "AUDIT_LOG", 382 ], + [ "RESET_TOKEN", 73 ], + [ "RECEIVES", 230 ], + [ "PERFORMS", 382 ], + [ "REQUESTS", 73 ], + [ "USER", 230 ], + [ "HOLDS", 382 ], + [ "ROLE", 499 ], + [ "OWNS", 230 ], + [ "REFRESH_TOKEN", 382 ] + ], + "cy": [ + [ "EMAIL_TOKEN", 115 ], + [ "AUDIT_LOG", 115 ], + [ "RESET_TOKEN", 274 ], + [ "RECEIVES", 274 ], + [ "PERFORMS", 274 ], + [ "REQUESTS", 485 ], + [ "USER", 485 ], + [ "HOLDS", 485 ], + [ "ROLE", 485 ], + [ "OWNS", 721 ], + [ "REFRESH_TOKEN", 721 ] + ], + "shift": [ + [ "RECEIVES,EMAIL_TOKEN,0", 0 ], + [ "RECEIVES,USER,0", 0 ], + [ "PERFORMS,AUDIT_LOG,0", 0 ], + [ "PERFORMS,USER,0", 0 ], + [ "REQUESTS,RESET_TOKEN,0", 0 ], + [ "REQUESTS,USER,0", 0 ], + [ "HOLDS,USER,0", 0 ], + [ "HOLDS,ROLE,0", 0 ], + [ "OWNS,REFRESH_TOKEN,0", 0 ], + [ "OWNS,USER,0", 0 ] + ], + "ratio": [] +} diff --git a/docs/database/mld/learn-dev_geo.json b/docs/database/mld/learn-dev_geo.json new file mode 100644 index 0000000..7d256b0 --- /dev/null +++ b/docs/database/mld/learn-dev_geo.json @@ -0,0 +1,43 @@ +{ + "width": 551, + "height": 819, + "cx": [ + [ "EMAIL_TOKEN", 230 ], + [ "AUDIT_LOG", 382 ], + [ "RESET_TOKEN", 73 ], + [ "RECEIVES", 230 ], + [ "PERFORMS", 382 ], + [ "REQUESTS", 73 ], + [ "USER", 230 ], + [ "HOLDS", 382 ], + [ "ROLE", 499 ], + [ "OWNS", 230 ], + [ "REFRESH_TOKEN", 382 ] + ], + "cy": [ + [ "EMAIL_TOKEN", 115 ], + [ "AUDIT_LOG", 115 ], + [ "RESET_TOKEN", 274 ], + [ "RECEIVES", 274 ], + [ "PERFORMS", 274 ], + [ "REQUESTS", 485 ], + [ "USER", 485 ], + [ "HOLDS", 485 ], + [ "ROLE", 485 ], + [ "OWNS", 721 ], + [ "REFRESH_TOKEN", 721 ] + ], + "shift": [ + [ "RECEIVES,EMAIL_TOKEN,0", 0 ], + [ "RECEIVES,USER,0", 0 ], + [ "PERFORMS,AUDIT_LOG,0", 0 ], + [ "PERFORMS,USER,0", 0 ], + [ "REQUESTS,RESET_TOKEN,0", 0 ], + [ "REQUESTS,USER,0", 0 ], + [ "HOLDS,USER,0", 0 ], + [ "HOLDS,ROLE,0", 0 ], + [ "OWNS,REFRESH_TOKEN,0", 0 ], + [ "OWNS,USER,0", 0 ] + ], + "ratio": [] +} diff --git a/docs/database/mld/learn-dev_mld.mcd b/docs/database/mld/learn-dev_mld.mcd new file mode 100644 index 0000000..4b5d4b1 --- /dev/null +++ b/docs/database/mld/learn-dev_mld.mcd @@ -0,0 +1,25 @@ +%%mocodo +::: +Email Token: token_id, token, expires_at, used_at, #user_id > User > user_id +: +Audit Log: log_id, action_type, entity_type, entity_id, description, ip_address, user_agent, was_successful, error_message, metadata, #user_id > User > user_id +::: + + +: +Reset Token: token_id, token, expires_at, used_at, ip_address, #user_id > User > user_id +::::::: + + +::: +User: user_id, username, email, password, first_name, last_name, is_active, is_verified, is_locked, failed_login_attempts, last_login_at, password_changed_at +: +Holds: #user_id > User > user_id, _#role_id > Role > role_id, assigned_at, assigned_by +: +Role: role_id, role_name, description, is_active +: + + +::::: +Refresh Token: token_id, token, expires_at, revoked_at, revoked_reason, ip_address, user_agent, last_used_at, #user_id > User > user_id +::: diff --git a/docs/database/mld/learn-dev_mld.md b/docs/database/mld/learn-dev_mld.md new file mode 100644 index 0000000..375dbd9 --- /dev/null +++ b/docs/database/mld/learn-dev_mld.md @@ -0,0 +1,9 @@ + + +- **Audit Log** (log_id, action_type, entity_type, entity_id, description, ip_address, user_agent, was_successful, error_message, metadata, _#user_id_) +- **Email Token** (token_id, token, expires_at, used_at, _#user_id_) +- **Holds** (_#user_id_, _#role_id_, assigned_at, assigned_by) +- **Refresh Token** (token_id, token, expires_at, revoked_at, revoked_reason, ip_address, user_agent, last_used_at, _#user_id_) +- **Reset Token** (token_id, token, expires_at, used_at, ip_address, _#user_id_) +- **Role** (role_id, role_name, description, is_active) +- **User** (user_id, username, email, password, first_name, last_name, is_active, is_verified, is_locked, failed_login_attempts, last_login_at, password_changed_at) diff --git a/docs/database/mld/learn-dev_mld.svg b/docs/database/mld/learn-dev_mld.svg new file mode 100644 index 0000000..d9984d6 --- /dev/null +++ b/docs/database/mld/learn-dev_mld.svg @@ -0,0 +1,171 @@ + + + + + + + + + + + + + + Email Token + token_id + + token + expires_at + used_at + #user_id + + + + + + + + + + + Audit Log + log_id + + action_type + entity_type + entity_id + description + ip_address + user_agent + was_successful + error_message + metadata + #user_id + + + + + + + + + + + Reset Token + token_id + + token + expires_at + used_at + ip_address + #user_id + + + + + + + + + + + User + user_id + + username + email + password + first_name + last_name + is_active + is_verified + is_locked + failed_login_attempts + last_login_at + password_changed_at + + + + + + + + + + + Holds + #user_id + + #role_id + + assigned_at + assigned_by + + + + + + + + + + + Role + role_id + + role_name + description + is_active + + + + + + + + + + + Refresh Token + token_id + + token + expires_at + revoked_at + revoked_reason + ip_address + user_agent + last_used_at + #user_id + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/database/mld/learn-dev_mld_geo.json b/docs/database/mld/learn-dev_mld_geo.json new file mode 100644 index 0000000..fc2fc81 --- /dev/null +++ b/docs/database/mld/learn-dev_mld_geo.json @@ -0,0 +1,24 @@ +{ + "width": 772, + "height": 694, + "cx": [ + [ "EMAIL_TOKEN", 302 ], + [ "AUDIT_LOG", 514 ], + [ "RESET_TOKEN", 102 ], + [ "USER", 302 ], + [ "HOLDS", 514 ], + [ "ROLE", 691 ], + [ "REFRESH_TOKEN", 514 ] + ], + "cy": [ + [ "EMAIL_TOKEN", 124 ], + [ "AUDIT_LOG", 124 ], + [ "RESET_TOKEN", 263 ], + [ "USER", 410 ], + [ "HOLDS", 410 ], + [ "ROLE", 410 ], + [ "REFRESH_TOKEN", 587 ] + ], + "shift": [], + "ratio": [] +}