Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
72 changes: 0 additions & 72 deletions .circleci/config.yml

This file was deleted.

87 changes: 87 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
name: Code quality

on:
push:
pull_request:

jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Set up Python 3.13
uses: actions/setup-python@v5
with:
python-version: '3.13'

- name: Install Poetry
run: pip install poetry

- name: Install dependencies
run: poetry config virtualenvs.create false && poetry install --no-root

- name: Run Ruff
run: ruff check src

- name: Run Pylint
run: pylint src/ ./app.py

test:
runs-on: ubuntu-latest
services:
mysql:
image: mysql:8.0
env:
MYSQL_ROOT_PASSWORD: ${{ secrets.MYSQL_ROOT_PASSWORD }}
MYSQL_DATABASE: games
MYSQL_USER: game
MYSQL_PASSWORD: ${{ secrets.MYSQL_PASSWORD }}
ports:
- 3306:3306
options: >-
--health-cmd="mysqladmin ping"
--health-interval=10s
--health-timeout=5s
--health-retries=9

steps:
- uses: actions/checkout@v4

- name: Set up Python 3.13
uses: actions/setup-python@v5
with:
python-version: '3.13'

- name: Install Poetry
run: pip install poetry

- name: Install dependencies
run: poetry config virtualenvs.create false && poetry install --no-root

- name: Create application configuration
run: |
cp configuration.json.dist configuration.json
sed -i 's/"db_host": "mysql"/"db_host": "127.0.0.1"/' configuration.json

- name: Configure MySQL authentication
env:
MYSQL_ROOT_PASSWORD: ${{ secrets.MYSQL_ROOT_PASSWORD }}
MYSQL_PASSWORD: ${{ secrets.MYSQL_PASSWORD }}
run: |
mysql --ssl-mode=DISABLED --get-server-public-key -h 127.0.0.1 -u root -p"$MYSQL_ROOT_PASSWORD" \
-e "ALTER USER 'game'@'%' IDENTIFIED WITH mysql_native_password BY '$MYSQL_PASSWORD';"

- name: Import test database
env:
MYSQL_PASSWORD: ${{ secrets.MYSQL_PASSWORD }}
run: mysql --ssl-mode=DISABLED -h 127.0.0.1 -u game -p"$MYSQL_PASSWORD" games < test/games_test.sql

- name: Start application
run: nohup gunicorn --workers=1 --bind=0.0.0.0:9000 app:app &

- name: Wait for application to be ready
run: sleep 5

- name: Run tests
run: python -m unittest discover .
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,6 @@ test/**/*.pyc
configuration.json
.history/
.vscode/
.venv/
.DS_Store

11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
# Changelog

## 5.0.0
* Bump to Python 3.13.
* Migration to Poetry.
* Added strong typing.
* Added a feature to manage magazines.
* Added an optionnal whitelist of IP adresses.
* We can link a note to a game version.
* API filters: for filter of type int, we can check against NULL by sending "Null" as the parameter.

BC Break: the URI for resources are now plural.

## 4.4.0
* Added a new feature: notes.
* Bump to Python 3.10.
Expand Down
77 changes: 77 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
# Project Context

This project is an API only application to manage a video games collection. It handles many elements, such as games, version, attributes of the games, the copy, transaction (selling or buying games), notes.

It provides a basic REST+JSON api.

## Architecture

The main controller of the project is the _app.py_ file at the root of the project. Is also where everything is loaded (configuration, create of the SQL connection...).

Then everything else (of the code) is stored in the _src_ folder.

* _connection_ contains the class to manage the connection to the database;
* _controller_ contains each of the controllers specific to a resource type. Note that they have a base class that contains common method, for instance for CRUD operations;
* _entity_ contrains classes that represent ressources;
* _exception_ contains all the various exception classes;
* _helper_ contains various helpers;
* _repository_ contains repositories classes that allow to load and persist ressources. Note that they have two levels of base classes that contains shared logic to interact with the DB for the first one, and performs common operations (like loading, filtering...) for the second one;
* _service_ contains services classes, where "business logic" might be stored to keep controllers lightweight.

## Entities

The entities represent the ressources managed by the API. One file = one class = one ressource.

Inside each entity class, we will find a constructor, getters and setters, and a method to serialize the object before returning it to the client.

But the most important part is probably all the metadata at the beginning of the class. Let's take this example:

```
expected_fields: dict[str, Any] = {
'title': {'field': 'title', 'method': '_title', 'required': True, 'type': 'text'},
'notes': {
'field': 'notes',
'method': '_notes',
'required': False,
'type': 'text',
'default': ''
},
}

authorized_extra_fields_for_filtering: dict[str, Any] = {
'id': {'field': 'id', 'origin': 'native', 'type': 'int'},
'versionCount': {'field': 'versionCount', 'origin': 'computed', 'type': 'int'}
}

table_name = 'games'
primary_key = 'id'
```

* _expected_fields_ is an array that lists all the fields in the MySQL table. The key of the array is the key in the payload. For each field, we have a _field_ value that contains the name of the MySQL field. _method_ is the suffix of the getters and setters. For each field, you also have a _type_ metadata, which represents the data type, and also an optionnal _default_ key for optional values.
* _authorized_extra_fields_for_filtering_ is an array of the key in the URL that represent fields we can filter on. We have the type, but also the 'origin', which is 'native' or 'computed', the first case being when it is a direct filter on the value in the database.
* _table_name_ is the name of the MySQL table.
* _primary_key_ is the name of the primary key.

Sometimes, things are more rigorous.
We have this case with the _copy_ entity. For some fields we can find things like this:

```
'type': 'strict-text',
'allowed_values': {
```
We notice that the type is "strict-text", hence we have a sub-array that contains all the allowed values for this field. We cannot list them all here, because they are specific to a field inside an entity.

The allowd types are:
* _strict-text_: is a list of supported choices;
* _text_: a text of undefined sized;
* _int_: well, it is an integer;
* _string_: short text (varchar 255).

The logical relation between the entities can be found in the _RESOURCES.md_ file in the _docs_ folder at the root of the project.

## Running or testing the app locally

* Running the app is not required for the AI agent.
* But testing it is usefull to detect added defects. Running the _make test_ command run the API test suite.
* Tests are located in the _tests_ folder.
* The stack uses _unittest_.
6 changes: 3 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ test:
make import_db && docker compose exec python bash -c 'make test_command_python'

linter:
docker compose exec python pylint --rcfile=standard.rc src/ ./app.py
docker compose exec python bash -c 'cd /code && poetry run ruff check src && poetry run pylint src/ ./app.py'

start:
docker compose up
Expand All @@ -21,9 +21,9 @@ import_db:
export_db:
docker compose exec mysql bash -c 'cd /code && mysqldump -u game -pazerty games > test/games_test.sql'

# updates the requirements from PIPENV (need to rebuild the pyton container after that)
# updates dependencies and regenerates the lock file (need to rebuild the python container after that)
requirements:
docker compose exec python bash -c "cd docker/python && pipenv lock -r > ./requirements.txt"
docker compose exec python bash -c "cd /code && poetry update"

## Containers internal command
import_db_command:
Expand Down
12 changes: 7 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
# GMG (Give Me a Game)
[![CircleCI](https://circleci.com/gh/ecourtial/gmg/tree/master.svg?style=svg)](https://circleci.com/gh/ecourtial/gmg/tree/master) [![Sonarcloud Status](https://sonarcloud.io/api/project_badges/measure?project=gmg&metric=alert_status)](https://sonarcloud.io/dashboard?id=gmg) [![Maintenance](https://img.shields.io/badge/Maintained%3F-yes-green.svg)](https://GitHub.com/ecourtial/gmg/graphs/commit-activity) [![Ask Me Anything !](https://img.shields.io/badge/Ask%20me-anything-1abc9c.svg)](https://GitHub.com/ecourtial/gmg) [![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg)](https://www.python.org/) [![GitHub license](https://img.shields.io/github/license/Naereen/StrapDown.js.svg)](https://github.com/ecourtial/gmg/blob/master/LICENSE)
[![CI](https://github.com/ecourtial/gmg/actions/workflows/ci.yaml/badge.svg)](https://github.com/ecourtial/gmg/actions/workflows/ci.yaml) [![Maintenance](https://img.shields.io/badge/Maintained%3F-yes-green.svg)](https://GitHub.com/ecourtial/gmg/graphs/commit-activity) [![Ask Me Anything !](https://img.shields.io/badge/Ask%20me-anything-1abc9c.svg)](https://GitHub.com/ecourtial/gmg) [![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg)](https://www.python.org/) [![GitHub license](https://img.shields.io/github/license/Naereen/StrapDown.js.svg)](https://github.com/ecourtial/gmg/blob/master/LICENSE)

## Description :notebook:

### A back-end application for your video games inventory

GMG is an educational test project. Being a PHP programmer, I developed this project using Python 3.x and Flask 2.
GMG is an educational test project. Being a PHP programmer, I developed this project using Python 3.x and Flask 3.x.
The goal of this application is to expose API endpoints to manage you video games collection, with various features.
There is no graphical interfaces, only API endpoints. Data is stored in MySQL.

Expand All @@ -19,7 +19,7 @@ I did not include a GUI because:

The developer who want to use this application is free to develop it's own front app connected through the REST endpoints, it is a classic. You can create a classy shiny state of the art front app or just a basic one using only one part of the features the back-end offers.

However, I developed my own front app, available [here](https://github.com/ecourtial/gmg-front), using PHP 8.1 and Symfony. You can use it if you don't have specific needs. Note: it does not include the support for all the features given by the back application.
However, I developed my own front app, available [here](https://github.com/ecourtial/gmg-front), using PHP and Symfony. You can use it if you don't have specific needs. Note: it does not include the support for all the features given by the back application, but almost everything though.

## Utilization

Expand All @@ -34,12 +34,14 @@ A basic documentation is available:
* Docker
* Nginx
* Gunicorn
* Python 3.10
* Flask 2
* Python 3
* Flask 3
* Circle CI
* unittest
* pylint
* MySQL 8
* Ruff
* Poetry

## Changelog

Expand Down
Loading
Loading