{"id":18339227,"url":"https://github.com/akotronis/booksapi","last_synced_at":"2026-04-04T21:33:28.221Z","repository":{"id":97073788,"uuid":"598060270","full_name":"akotronis/BooksAPI","owner":"akotronis","description":"A CRUD rest api app for books fetched from Openlibrary","archived":false,"fork":false,"pushed_at":"2023-02-15T11:07:34.000Z","size":66,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-10-10T05:13:35.488Z","etag":null,"topics":["asyncio","docker","flask","flask-smorest","jwt-authentication","pytest","python","rest-api","sqlalchemy"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/akotronis.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-02-06T10:07:35.000Z","updated_at":"2025-08-29T13:55:48.000Z","dependencies_parsed_at":null,"dependency_job_id":"4e5a6716-201a-4782-913e-b0536576b3fa","html_url":"https://github.com/akotronis/BooksAPI","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/akotronis/BooksAPI","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/akotronis%2FBooksAPI","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/akotronis%2FBooksAPI/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/akotronis%2FBooksAPI/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/akotronis%2FBooksAPI/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/akotronis","download_url":"https://codeload.github.com/akotronis/BooksAPI/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/akotronis%2FBooksAPI/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31415110,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-04T20:09:54.854Z","status":"ssl_error","status_checked_at":"2026-04-04T20:09:44.350Z","response_time":60,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["asyncio","docker","flask","flask-smorest","jwt-authentication","pytest","python","rest-api","sqlalchemy"],"created_at":"2024-11-05T20:16:53.475Z","updated_at":"2026-04-04T21:33:28.182Z","avatar_url":"https://github.com/akotronis.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Flask BooksAPI\n\nA CRUD rest api app for books fetched from [Openlibrary](https://openlibrary.org/developers/api)\n\n# Implementation\n\n## Models\n\nDatabase models were created according to the logic indicated in the below diagram\n\n[Database diagram](https://drawsql.app/teams/akotronis-team/diagrams/books)\n\n(Folder **models**)\n\n- `book`\n- `author`\n- `work`\n- `book_author`\n- `book_work`\n- `user`\n\nAs indicated from openlibrary data,\n\n- A **book** [may have](https://openlibrary.org/books/OL1017798M.json) multiple **authors** and belong to multiple **works**\n- A **work** [may contain](https://openlibrary.org/works/OL45804W/Fantastic_Mr_Fox) multiple **books** and\n- An **author** may have written multiple **books**\n\nImplementation with [`flask-sqlalchemy`](https://flask-sqlalchemy.palletsprojects.com/en/3.0.x/)\n\n## Authentication\n\nUser authentication implemented with [`flask-jwt-extended`](https://flask-jwt-extended.readthedocs.io/en/stable/)\n\n(Currently protected endpoint: `/books` (and `/v2/books`, see **versioning** below) \u0026rarr; **GET**. Uncomment decorator `@jwt_required()` to protect others)\n\n## Data transfer validation/(de)serialization\n\nImplemented with [`marshmallow`](https://marshmallow.readthedocs.io/en/stable/)\n\n(Folder **schemas**)\n\n- `olib_book`\n- `book`\n- `author`\n- `work`\n- `book_author`\n- `book_work`\n- `user`\n\n## Endpoints\n\n(Folder **resources**)\n\n- `olib_book`\n- `book`\n- `author`\n- `work`\n- `user`\n\nIn openlib there are books without registered _authors_, which suggests that _authors_ should be handled by separate endpoints, decoupled from _book_ endpoints implementation.\n\nSo the endpoints are the below:\n\n### Books\n\n- `/olib-books` \u0026rarr; **GET**\n  - Fetch book codes defined in `book_codes.py` from open-library, extracts info, clean tables and populate database\n  - Accepts query `async=\u003cbool\u003e` based on which the get requests to open library are performed _sequentially_ (`async=False`) or _concurrently_ (`async=True`) using `requests` module in combination with `asyncio`. Second option is **significantly faster**.\n- `/books/\u003cbook_id\u003e` \u0026rarr; **GET, PUT, DELETE** a book with a specific `book_id`\n- `/books` \u0026rarr; **POST**\n  - Create a book with `{'code': \u003cbook_code_str\u003e, 'title': \u003cbook_title_str\u003e}`\n- `/books` \u0026rarr; **GET**\n  - Get list of all books in database. May filter results by adding query `contains=\u003cstring\u003e` (Filters by `\u003cstring\u003e` in `title`, case insensitive)\n- `/books/\u003cbook_id\u003e/authors/\u003cauthor_id\u003e` \u0026rarr; **POST**\n  - Link a book to an author. Add a row to `books_authors` table\n- `/books/\u003cbook_id\u003e/works/\u003cwork_id\u003e` \u0026rarr; **POST**\n  - Link a book to a work. Add a row to `books_works` table\n\n### Authors\n\n- `/authors/\u003cauthor_id\u003e` \u0026rarr; **GET, PUT, DELETE** an author with a specific `author_id`\n- `/authors` \u0026rarr; **POST**\n  - Create an author with `{'code': \u003cauthor_code_str\u003e}`\n- `/authors` \u0026rarr; **GET**\n  - Get list of all authors in database\n\n### Works\n\n- `/works/\u003cwork_id\u003e` \u0026rarr; **GET, PUT, DELETE** a work with a specific `work_id`\n- `/works` \u0026rarr; **POST**\n  - Create a work with `{'code': \u003cwork_code_str\u003e}`\n- `/works` \u0026rarr; **GET**\n  - Get list of all works in database\n\n### Users\n\n- `/users/register` \u0026rarr; **POST** register a user with `{'username': \u003cusername\u003e, 'password': \u003cpassword\u003e}`\n- `/users/login` \u0026rarr; **POST** login a user with `{'username': \u003cusername\u003e, 'password': \u003cpassword\u003e}`\n- `/users/logout` \u0026rarr; **POST** logout a user\n- `/users/\u003cuser_id\u003e` \u0026rarr; **GET, DELETE** a user by `user_id`\n\n### Rules\n\n- `/rules` \u0026rarr; **GET** returns info about all available endpoints/methods and their versions\n\n## Versioning\n\nVersioning with Blueprints is implemented in file `versioning.py`\n\n- Endpoints with prefix `/v{i}` are exposed for all resources/methods where `{i}` corresponds to the implemented resources/methods versions\n- Endpoints **without** `/v{i}` prefix correspond to the latest implemented versions\n- Implemented versions are:\n  - `v1, v2` for `/books` \u0026rarr; **GET** (so we have `/v1/books` \u0026rarr; **GET** | _unprotected_, and `/v2/books`(=`/books`) \u0026rarr; **GET** | _protected_)\n  - `v1` for all other resources/methods\n- `/rules` has no versions\n\n# Instructions (Run)\n\nMake `.env` file as in `.env.example`\n\n`WORK_ENV=prod/dev/test`. If empty, defaults to `dev`. See `config.py/run.py` for details.\n\n## Run app outside Docker\n\n`\u003e\u003e\u003e python run.py`\n\n## Build-Run the Dockerfile locally\n\nWith Docker (Desktop) installed, on the folder where `Dockerfile` is:\n\n- `\u003e\u003e\u003e docker build -t books-api-image .`\n- `\u003e\u003e\u003e docker run --rm -it --name books-api-container -p 5000:5000 -w /app -v ${PWD}:/app books-api-image` (Powershell) OR\n- `\u003e\u003e\u003e docker run --rm -it --name books-api-container -p 5000:5000 -w /app -v \"%cd%\":/app books-api-image` (Terminal)\n\n# Instructions (Test)\n\nIn root folder:\n\n- `\u003e\u003e\u003e pytest -s` to run all tests\n- `\u003e\u003e\u003e pytest --strict-markers -m \u003cmarker-name\u003e -s` to run tests by marker, where `marker-name` s can be found in `pytest.ini` file\n\n# TODO\n\n- Implement versioning with Blueprints \u0026check;\n- Documentation with `flask-smorest/swagger`\n- Use `flask-migrate` for migrations\n- Use `gunicorn` as a server\n- Unit testing \u0026check;\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fakotronis%2Fbooksapi","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fakotronis%2Fbooksapi","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fakotronis%2Fbooksapi/lists"}