diff --git a/.circleci/config.yml b/.circleci/config.yml index 6ee4522..5146aa3 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -53,6 +53,7 @@ jobs: command: | mysql -h mysql -u game -pazerty games < test/games_test.sql nohup gunicorn --workers=1 --bind=0.0.0.0:9000 app:app & + sleep 5 make test_command_python linter: diff --git a/.gitignore b/.gitignore index 5ee611c..24d7f37 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,5 @@ __pycache__/ docker/data/db test/**/*.pyc configuration.json +.history/ +.vscode/ diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..315b461 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,8 @@ +# Changelog + +## 4.1.0 + +### New features +* The copy entity has a new _is_rom_ field to define if the copy is a ROM or not (for emulation for instance). +A migration file is available in the _migrations_ folder (see [Migrations and update guide](docs/MIGRATIONS.md)). +* The copy _casingType_ field accepts a new value: _Plastic tube_. diff --git a/Makefile b/Makefile index 5374a64..9e26d96 100644 --- a/Makefile +++ b/Makefile @@ -1,29 +1,29 @@ .PHONY: test test: - make import_db && docker-compose exec python bash -c 'make test_command_python' + 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 pylint --rcfile=standard.rc src/ ./app.py start: - docker-compose up + docker compose up python: - docker-compose exec python bash + docker compose exec python bash mysql: - docker-compose exec mysql bash -c 'mysql -u game -pazerty games' + docker compose exec mysql bash -c 'mysql -u game -pazerty games' import_db: - docker-compose exec mysql bash -c 'cd /code && make import_db_command' + docker compose exec mysql bash -c 'cd /code && make import_db_command' export_db: - docker-compose exec mysql bash -c 'cd /code && mysqldump -u game -pazerty games > test/games_test.sql' + 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) requirements: - docker-compose exec python bash -c "cd docker/python && pipenv lock -r > ./requirements.txt" + docker compose exec python bash -c "cd docker/python && pipenv lock -r > ./requirements.txt" ## Containers internal command import_db_command: @@ -31,3 +31,6 @@ import_db_command: test_command_python: python -m unittest discover . + +migration: + docker compose exec mysql bash -c 'cd /code && mysql -u game -pazerty games < migrations/${FILE}.sql' diff --git a/README.md b/README.md index d5dfa39..895e8f3 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,7 @@ A basic documentation is available: * [How to install the project](docs/SETUP.md) * [Introduction to the API](docs/API.md) * [Resources managed by the application and their API representation](docs/RESOURCES.md) +* [Migrations and update guide](docs/MIGRATIONS.md) ## Stack :light_rail: @@ -39,3 +40,7 @@ A basic documentation is available: * unittest * pylint * MySQL 8 + +## Changelog + +The changelog is available [here](CHANGELOG.md) diff --git a/docker-compose.yml b/docker-compose.yml index 76037c1..4a059a5 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -14,7 +14,6 @@ services: build: docker/python container_name: python_gmg working_dir: /code - restart: always volumes: - .:/code depends_on: @@ -34,8 +33,3 @@ services: - MYSQL_DATABASE=games - MYSQL_USER=game - MYSQL_PASSWORD=azerty - -networks: - default: - external: - name: shared-gmg \ No newline at end of file diff --git a/docker/mysql/Dockerfile b/docker/mysql/Dockerfile index 5d62e8f..8973a66 100644 --- a/docker/mysql/Dockerfile +++ b/docker/mysql/Dockerfile @@ -1,5 +1,3 @@ -# Use the default image. Do not use like those guys who use a Linux distro then install nginx -FROM mysql:8 +FROM mysql:8.0-debian -# Install nano because using vi is like using a donkey at the age of intergalactic travel RUN apt-get update && apt-get -q -y install nano make diff --git a/docker/python/Dockerfile b/docker/python/Dockerfile index 2c51127..9be87db 100644 --- a/docker/python/Dockerfile +++ b/docker/python/Dockerfile @@ -12,7 +12,7 @@ RUN pip install pipenv COPY requirements.txt . RUN pip3 install --no-cache-dir -r requirements.txt -RUN pip install pylint +RUN pip install pylint==2.13.9 # --reload: in dev mode, ask gunicorn to restart worker on any source file change CMD ["gunicorn", "--workers=1", "--threads=2", "--bind=0.0.0.0:9000", "--reload", "app:app"] diff --git a/docs/MIGRATIONS.md b/docs/MIGRATIONS.md new file mode 100644 index 0000000..018f7a5 --- /dev/null +++ b/docs/MIGRATIONS.md @@ -0,0 +1,9 @@ +# Migrations and update guide + +* 1) Stop serving the application (just keep MySQL running). +* 2) Get and update the source code form the last version (tip: download everything from Github and replace it in your folder). +* 3) Run all the migrations you need using the following command: +```make migration FILE={migration_file}``` +for instance +```make migration FILE=4.1.0```. +* 4) Restart your server. diff --git a/docs/RESOURCES.md b/docs/RESOURCES.md index 7b2cbd2..ae5d377 100644 --- a/docs/RESOURCES.md +++ b/docs/RESOURCES.md @@ -95,21 +95,22 @@ The application relies on 6 resources (not including the users). | Field | Type | Editable | Unique | Role | Notes | |--------------------------|-------|----------|--------|-----------------------------------------------------------------------------|-----------------------------------------| -| id | int | No | Yes | The version unique id. | | -| versionId | int | Yes | No | The id of the version the story relates to. | | -| original | bool | Yes | No | Is the copy an original (not a copy from an original)? | | -| language | string | Yes | No | Any language value. We recommend to use ISO standards, or _multi_ if mutilanguage. | | -| boxType | string| Yes | No | Type of the box (See allowed types below). | | -| casingType | string| Yes | No | Casing type of the box (See allowed types below). | | -| supportType | string| Yes | No | The game support type (See allowed types below). | | -| onCompilation | bool | Yes | No | Is the copy of the game on a compilation? | | -| reedition | bool | Yes | No | Is the copy of the game on a reedition, like a platinum, a classic one? | | -| hasManual | bool | Yes | No | Does the copy has a manual? | | -| status | string| Yes | No | Do you currently have this copy (See allowed types below)? | | -| type | string| Yes | No | Type of the copy (See allowed types below)? | | -| comments | string| Yes | No | Commments. | | -| platformName | string| No | No | The name of the platform. | | -| gameTitle | string| No | No | The title of the game related to this copy. | | +| id | int | No | Yes | The version unique id. | | +| versionId | int | Yes | No | The id of the version the story relates to. | | +| original | bool | Yes | No | Is the copy an original (not a copy from an original)? | | +| language | string| Yes | No | Any language value. We recommend to use ISO standards, or _multi_ if mutilanguage. | | +| boxType | string| Yes | No | Type of the box (See allowed types below). | | +| casingType | string| Yes | No | Casing type of the box (See allowed types below). | | +| supportType | string| Yes | No | The game support type (See allowed types below). | | +| onCompilation | bool | Yes | No | Is the copy of the game on a compilation? | | +| reedition | bool | Yes | No | Is the copy of the game on a reedition, like a platinum, a classic one? | | +| hasManual | bool | Yes | No | Does the copy has a manual? | | +| status | string| Yes | No | Do you currently have this copy (See allowed types below)? | | +| type | string| Yes | No | Type of the copy (See allowed types below)? | | +| is_rom | bool | Yes | No | Is the copy a ROM? | | +| comments | string| Yes | No | Commments. | | +| platformName | string| No | No | The name of the platform. | | +| gameTitle | string| No | No | The title of the game related to this copy. | | ### Allowed types for _boxType_ diff --git a/docs/SETUP.md b/docs/SETUP.md index 8718c13..fa1a090 100644 --- a/docs/SETUP.md +++ b/docs/SETUP.md @@ -2,10 +2,14 @@ ## Locally (DEV) -In the root folder, copy the file _configuration.json.dist_ to _configuration.json_ and fill it with your values. The MySQL credentials are visible in the _docker-compose.yml_ in the root folder. +In the root folder, copy the file _configuration.json.dist_ to _configuration.json_ and fill it with your values (the Google Captcha values are mandatory). The MySQL credentials are visible in the _docker-compose.yml_ in the root folder. Next, just do a __make start__, and then run __make test__ to run the tests and import the local DB with test features. You're good to go! +Ther test DB it just imported has a user. You can login with the following credentials : +* username: _mephistophelesz_ +* password: _barz_ + ## Production _Reminder_: the empty database contains a default user. @@ -17,6 +21,7 @@ Change them as soon as your project is running! The usual process to setup a project is the following: * Import the empty database (available at the root of the folder). +* Run all the migrations ([see here](./MIGRATIONS.md)) * Deploy the code. * Configure the application. * Start the application. diff --git a/migrations/4.1.0.sql b/migrations/4.1.0.sql new file mode 100644 index 0000000..8eb7f05 --- /dev/null +++ b/migrations/4.1.0.sql @@ -0,0 +1 @@ +ALTER TABLE copies ADD is_rom TINYINT UNSIGNED NOT NULL DEFAULT 0 AFTER type; diff --git a/src/entity/copy.py b/src/entity/copy.py index e6f276d..c4e1708 100644 --- a/src/entity/copy.py +++ b/src/entity/copy.py @@ -39,6 +39,7 @@ class Copy(AbstractEntity): 'Cardboard sleeve', 'Paper Sleeve', 'Plastic Sleeve', + 'Plastic tube', 'Other', 'None' } @@ -103,6 +104,12 @@ class Copy(AbstractEntity): 'type': 'text', 'default': '' }, + 'isROM': { + 'field': 'is_rom', + 'method': '_is_rom', + 'required': False, + 'type': 'int' + }, } authorized_extra_fields_for_filtering = { @@ -131,6 +138,7 @@ def __init__( status, type, comments, + is_rom = None, platform_name = None, game_title = None, transaction_count = None, @@ -147,6 +155,7 @@ def __init__( self.has_manual = bool(has_manual) self.status = status self.type = type + self.is_rom = bool(is_rom) self.comments = comments self.platform_name = platform_name self.game_title = game_title @@ -188,6 +197,9 @@ def get_status(self): def get_type(self): return self.type + def get_is_rom(self): + return self.is_rom + def get_comments(self): return self.comments @@ -224,6 +236,9 @@ def set_status(self, status): def set_type(self, type): self.type = type + def set_is_rom(self, is_rom): + self.is_rom = bool(is_rom) + def set_comments(self, comments): self.comments = comments diff --git a/standard.rc b/standard.rc index 3f8caac..f15c425 100644 --- a/standard.rc +++ b/standard.rc @@ -193,8 +193,7 @@ max-module-lines=1000 # separator` is used to allow tabulation in dicts, etc.: {1 : 1,\n222: 2}. # `trailing-comma` allows a space between comma and closing bracket: (a, ). # `empty-line` allows space-only lines. -no-space-check=trailing-comma, - dict-separator + # Allow the body of a class to be on the same line as the declaration if body # contains single statement. diff --git a/test/functional/test_copies.py b/test/functional/test_copies.py index 5ed9bae..3908319 100644 --- a/test/functional/test_copies.py +++ b/test/functional/test_copies.py @@ -61,6 +61,27 @@ def test_create_invalid_types(self): self.assertEqual(400, resp.status_code) self.assertEqual({'message': "The field 'boxType' does not support the value 'Big boxe'. Supported values are: Big box, Cartridge box, None, Other.", 'code': 11}, resp.json()) + def test_create_invalid_casing_type(self): + payload = { + "versionId": 1, + "original": True, + 'language': 'fr', + "boxType": "Cartridge box", + "casingType": "CD-likesss", + 'supportType': 'CD-ROM', + "onCompilation": True, + "reedition": True, + "hasManual": False, + "status": "In", + "comments": "Found it somewhere" + } + + resp = self.api_call('post', 'copy', payload, True) + + self.assertEqual(400, resp.status_code) + self.assertEqual({'message': "The field 'casingType' does not support the value 'CD-likesss'. Supported values are: CD-like, Cardboard sleeve, DVD-like, None, Other, Paper Sleeve, Plastic Sleeve, Plastic tube.", 'code': 11}, resp.json()) + + def test_get_copy(self): # Does not exist resp = self.api_call('get', 'copy/666', {}, True) @@ -85,6 +106,7 @@ def test_get_copy(self): "status": "In", 'type': 'Physical', "comments": "Bought it in 2004", + 'isROM': False, 'gameTitle': 'Tonic Trouble', 'platformName': 'PC', 'transactionCount': 2, @@ -118,6 +140,7 @@ def test_create_update_delete_success(self): self.assertEqual(200, resp.status_code) copy_id = str(resp.json()["id"]) payload['id'] = int(copy_id) + payload['isROM'] = False self.assertEqual(payload, resp.json()) resp = self.api_call('get', 'copy/' + str(copy_id), None, True) @@ -136,6 +159,7 @@ def test_create_update_delete_success(self): "hasManual": False, "status": "In", "comments": "Found it somewhere", + "isROM": True, 'platformName': 'PC', 'gameTitle': 'Tonic Trouble', 'transactionCount': 0, @@ -144,6 +168,7 @@ def test_create_update_delete_success(self): resp = self.api_call('patch', 'copy/' + copy_id, payload, True) payload['id'] = int(copy_id) + payload['isROM'] = True self.assertEqual(200, resp.status_code) self.assertEqual(payload, resp.json()) diff --git a/test/games_test.sql b/test/games_test.sql index 8c4a954..4fa6391 100644 --- a/test/games_test.sql +++ b/test/games_test.sql @@ -1,8 +1,8 @@ --- MySQL dump 10.13 Distrib 8.0.28, for Linux (x86_64) +-- MySQL dump 10.13 Distrib 8.0.29, for Linux (x86_64) -- -- Host: localhost Database: games -- ------------------------------------------------------ --- Server version 8.0.28 +-- Server version 8.0.29 /*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; /*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; @@ -35,6 +35,7 @@ CREATE TABLE `copies` ( `has_manual` tinyint unsigned NOT NULL, `status` varchar(255) NOT NULL DEFAULT 'In', `type` varchar(255) NOT NULL, + `is_rom` tinyint unsigned NOT NULL DEFAULT '0', `comments` text, PRIMARY KEY (`copy_id`), KEY `version_id` (`version_id`), @@ -48,7 +49,7 @@ CREATE TABLE `copies` ( LOCK TABLES `copies` WRITE; /*!40000 ALTER TABLE `copies` DISABLE KEYS */; -INSERT INTO `copies` VALUES (1,348,1,'fr','Big box','CD-like','CD-ROM',0,0,1,'In','Physical','Bought it in 2004'),(2,349,1,'fr','none','Cardboard sleeve','CD-ROM',1,1,0,'In','Physical','Got it with my cereals'),(3,245,1,'fr','None','CD-like','CD-ROM',1,1,0,'In','Physical','pues'); +INSERT INTO `copies` VALUES (1,348,1,'fr','Big box','CD-like','CD-ROM',0,0,1,'In','Physical',0,'Bought it in 2004'),(2,349,1,'fr','none','Cardboard sleeve','CD-ROM',1,1,0,'In','Physical',0,'Got it with my cereals'),(3,245,1,'fr','None','CD-like','CD-ROM',1,1,0,'In','Physical',0,'pues'); /*!40000 ALTER TABLE `copies` ENABLE KEYS */; UNLOCK TABLES; @@ -292,4 +293,4 @@ UNLOCK TABLES; /*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; /*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; --- Dump completed on 2022-06-22 7:43:52 +-- Dump completed on 2022-07-21 6:36:31