diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ae0011d..15921e1 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -19,7 +19,10 @@ jobs: - name: Install Python dependencies run: | pip install poetry - poetry install --no-dev + poetry install + - name: Run Test + run: | + poetry run pytest --no-cov # - name: Install Node dependencies # run: npm install # - run: cp .env.example .env diff --git a/.github/workflows/ci-changelog.yml b/.github/workflows/ci-changelog.yml index ab4ecf7..8ae1372 100644 --- a/.github/workflows/ci-changelog.yml +++ b/.github/workflows/ci-changelog.yml @@ -14,13 +14,13 @@ jobs: with: ref: master - name: Update Changelog - uses: heinrichreimer/github-changelog-generator-action@v2.1.1 + uses: heinrichreimer/github-changelog-generator-action@v2.2 with: token: ${{ secrets.GITHUB_TOKEN }} issues: true issuesWoLabels: true - pullRequests: false - prWoLabels: false + pullRequests: true + prWoLabels: true unreleased: true addSections: '{"documentation":{"prefix":"**Documentation:**","labels":["documentation"]}}' - uses: stefanzweifel/git-auto-commit-action@v4 diff --git a/.github/workflows/publish-docs.yml b/.github/workflows/publish-docs.yml index 3dceef4..778aeb3 100644 --- a/.github/workflows/publish-docs.yml +++ b/.github/workflows/publish-docs.yml @@ -22,7 +22,7 @@ jobs: python-version: ${{ env.PYTHON_VERSION }} - name: Python Poetry Action - uses: abatilo/actions-poetry@v2.1.0 + uses: abatilo/actions-poetry@v2.1.2 with: poetry-version: ${{ env.POETRY_VERSION }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index eefa920..0d7897e 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -21,7 +21,7 @@ jobs: with: ref: master - name: Update Changelog - uses: heinrichreimer/github-changelog-generator-action@v2.1.1 + uses: heinrichreimer/github-changelog-generator-action@v2.2 with: token: ${{ secrets.GITHUB_TOKEN }} issues: true @@ -53,7 +53,7 @@ jobs: - name: Get Changelog Entry id: changelog_reader - uses: mindsers/changelog-reader-action@v1 + uses: mindsers/changelog-reader-action@v2 with: version: ${{ env.CURRENT_VERSION }} path: ./CHANGELOG.md diff --git a/.gitignore b/.gitignore index c581b61..5101875 100644 --- a/.gitignore +++ b/.gitignore @@ -73,3 +73,6 @@ maker_hub/static/cache_manifest.json !.vscode/launch.json site/* +app/coverage.xml +coverage.xml +htmlcov diff --git a/CHANGELOG.md b/CHANGELOG.md index 56a3b4f..4ce5a61 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ - Add pages site \(docs\) link to README [\#51](https://github.com/madeinoz67/maker-hub/issues/51) +**Closed issues:** + +- Update About page with new Repo [\#59](https://github.com/madeinoz67/maker-hub/issues/59) + ## [v2021.0.0-dev1](https://github.com/madeinoz67/maker-hub/tree/v2021.0.0-dev1) (2021-05-22) **Implemented enhancements:** diff --git a/Pipfile b/Pipfile new file mode 100644 index 0000000..71e4f7c --- /dev/null +++ b/Pipfile @@ -0,0 +1,11 @@ +[[source]] +url = "https://pypi.org/simple" +verify_ssl = true +name = "pypi" + +[packages] + +[dev-packages] + +[requires] +python_version = "3.9" diff --git a/app/api/.gitkeep b/app/api/__init__.py similarity index 100% rename from app/api/.gitkeep rename to app/api/__init__.py diff --git a/app/api/part_api.py b/app/api/part_api.py new file mode 100644 index 0000000..9eeeab5 --- /dev/null +++ b/app/api/part_api.py @@ -0,0 +1,24 @@ +import fastapi + +from app.schema.datatable import DataTableRequest, DataTableResponse +from app.services import part_service + +api = fastapi.APIRouter() + + +@api.post("/api/part/datatable") +async def table_datasource(request: DataTableRequest) -> DataTableResponse: + """Parts Datatable Source + + Returns: + [TableResponse]: [DataTable Ajax Response] + """ + + draw = request.draw + + data = await part_service.get_latest_parts( + start=request.start, limit=request.length + ) + return DataTableResponse( + draw=draw, recordsTotal=100, recordsFiltered=100, data=data + ) diff --git a/app/main.py b/app/main.py index 842b1dc..2610e9f 100644 --- a/app/main.py +++ b/app/main.py @@ -1,7 +1,7 @@ import os import fastapi import fastapi_chameleon -import uvicorn + from starlette.staticfiles import StaticFiles from dotenv import load_dotenv @@ -11,8 +11,13 @@ from app.views import storage from app.views import reports +from app.api import part_api -app = fastapi.FastAPI() +app = fastapi.FastAPI( + title="Maker Hub", + description="Personal Hub for makers: Manage Parts, projects, ideas, documentation, parts and footprints etc", + version="2021.0.0-dev1", +) def main(): @@ -37,6 +42,11 @@ def configure_routes(): static_folder = os.path.join(folder, "static") static_folder = os.path.abspath(static_folder) app.mount("/static", StaticFiles(directory=static_folder), name="static") + + # API endpoints + app.include_router(part_api.api) + + # Webpages app.include_router(home.router) app.include_router(parts.router) app.include_router(projects.router) diff --git a/app/schema/__init__.py b/app/schema/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/schema/datatable.py b/app/schema/datatable.py new file mode 100644 index 0000000..c22ebe6 --- /dev/null +++ b/app/schema/datatable.py @@ -0,0 +1,75 @@ +from typing import List, Optional + +from pydantic import BaseModel + +from app.schema.part import Part + + +class Search(BaseModel): + value: str = "" + regex: bool = False + + +class Order(BaseModel): + column: int + dir: str = "asc" + + +class Column(BaseModel): + name: str + data: str + searchable: bool = True + orderable: bool = True + search: Search + + +class DataTableBase(BaseModel): + draw: int + + +class DataTableRequest(DataTableBase): + start: int + length: int + search: Search + order: List[Order] + columns: List[Column] + + +class DataTableResponse(DataTableBase): + recordsTotal: int + recordsFiltered: int + data: List[Part] = [] + error: Optional[str] = None + + +# Example DataTable generated Query +# { +# "draw": 1, +# "columns": [ +# { +# "data": "id", +# "name": "", +# "searchable": false, +# "orderable": false, +# "search": {"value": "", "regex": false}, +# }, +# { +# "data": "name", +# "name": "", +# "searchable": true, +# "orderable": true, +# "search": {"value": "", "regex": false}, +# }, +# { +# "data": "description", +# "name": "", +# "searchable": true, +# "orderable": true, +# "search": {"value": "", "regex": false}, +# }, +# ], +# "order": [{"column": 1, "dir": "asc"}], +# "start": 0, +# "length": 100, +# "search": {"value": "", "regex": false}, +# } diff --git a/app/schema/part.py b/app/schema/part.py new file mode 100644 index 0000000..00b994d --- /dev/null +++ b/app/schema/part.py @@ -0,0 +1,7 @@ +from pydantic import BaseModel + + +class Part(BaseModel): + id: str + name: str + description: str diff --git a/app/services/part_service.py b/app/services/part_service.py index bad3fa7..3fbdfdc 100644 --- a/app/services/part_service.py +++ b/app/services/part_service.py @@ -1,33 +1,526 @@ from typing import List -def get_part_count() -> int: +async def get_part_count() -> int: return 283 -def get_total_stock() -> int: - return 1_00 +async def get_total_stock() -> int: + return 1_000 -def get_stock_value() -> int: - return 1_500 +async def get_stock_value() -> float: + return 1_500.00 -def get_latest_parts(limit: int = 5) -> List: - return [ +async def get_latest_parts( + start: int = 0, limit: int = 5 +) -> List: # TODO change return to List[Part] when db implemented + + start = max(0, start) + limit = max(0, limit) + + data = [ + { + "id": 1, + "name": "felis", + "description": "feugiat non pretium quis lectus suspendisse potenti in eleifend quam a odio in hac habitasse platea dictumst maecenas ut massa quis augue luctus tincidunt nulla mollis molestie lorem quisque ut erat", + }, + { + "id": 2, + "name": "adipiscing", + "description": "praesent blandit nam nulla integer pede justo lacinia eget tincidunt eget tempus vel pede morbi porttitor lorem id ligula suspendisse ornare consequat lectus in est risus", + }, + { + "id": 3, + "name": "augue", + "description": "leo maecenas pulvinar lobortis est phasellus sit amet erat nulla tempus vivamus in felis eu sapien cursus vestibulum proin eu mi nulla ac enim in tempor turpis nec euismod scelerisque quam turpis adipiscing lorem vitae mattis nibh ligula nec sem duis aliquam convallis nunc proin at turpis a pede posuere", + }, + { + "id": 4, + "name": "vulputate", + "description": "amet lobortis sapien sapien non mi integer ac neque duis bibendum morbi non quam nec dui luctus rutrum nulla tellus in sagittis dui vel nisl duis ac nibh fusce lacus purus aliquet at feugiat non pretium quis lectus suspendisse potenti in", + }, + { + "id": 5, + "name": "vestibulum", + "description": "porttitor id consequat in consequat ut nulla sed accumsan felis ut at dolor quis odio consequat varius integer ac", + }, + { + "id": 6, + "name": "massa", + "description": "lectus aliquam sit amet diam in magna bibendum imperdiet nullam orci pede venenatis non sodales sed tincidunt eu felis fusce posuere felis sed lacus morbi sem mauris laoreet ut rhoncus aliquet pulvinar sed nisl nunc rhoncus dui vel sem", + }, + { + "id": 7, + "name": "fusce", + "description": "faucibus cursus urna ut tellus nulla ut erat id mauris vulputate elementum nullam varius nulla facilisi cras non velit nec nisi vulputate nonummy maecenas tincidunt lacus at velit vivamus vel nulla eget eros", + }, + { + "id": 8, + "name": "non", + "description": "turpis a pede posuere nonummy integer non velit donec diam neque vestibulum eget vulputate ut ultrices vel augue vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae donec pharetra magna", + }, + { + "id": 9, + "name": "turpis", + "description": "nulla nisl nunc nisl duis bibendum felis sed interdum venenatis turpis enim blandit mi in porttitor pede justo eu massa donec dapibus duis at velit eu est congue elementum in hac habitasse", + }, + { + "id": 10, + "name": "eu", + "description": "in sapien iaculis congue vivamus metus arcu adipiscing molestie hendrerit at vulputate vitae nisl aenean lectus pellentesque eget nunc donec quis", + }, + { + "id": 11, + "name": "est", + "description": "molestie hendrerit at vulputate vitae nisl aenean lectus pellentesque eget nunc donec quis orci eget orci vehicula condimentum curabitur in libero ut massa volutpat convallis morbi odio odio elementum eu interdum eu tincidunt in leo maecenas pulvinar lobortis est phasellus sit amet", + }, + { + "id": 12, + "name": "vestibulum", + "description": "suspendisse potenti cras in purus eu magna vulputate luctus cum sociis natoque penatibus et magnis dis parturient montes", + }, + { + "id": 13, + "name": "turpis", + "description": "turpis elementum ligula vehicula consequat morbi a ipsum integer a nibh in quis justo maecenas rhoncus aliquam lacus morbi quis tortor id nulla ultrices aliquet maecenas leo odio condimentum id luctus nec molestie sed justo pellentesque viverra pede ac diam cras pellentesque volutpat dui", + }, + { + "id": 14, + "name": "cum", + "description": "vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae mauris viverra diam vitae quam suspendisse potenti nullam porttitor lacus at turpis donec posuere metus vitae ipsum aliquam non mauris morbi non lectus aliquam sit amet diam in magna bibendum imperdiet", + }, + { + "id": 15, + "name": "nulla", + "description": "id nulla ultrices aliquet maecenas leo odio condimentum id luctus nec molestie sed justo pellentesque viverra pede ac diam cras pellentesque volutpat dui maecenas tristique est et tempus semper est quam pharetra magna ac consequat metus sapien ut nunc vestibulum ante ipsum", + }, + { + "id": 16, + "name": "justo", + "description": "tempus vel pede morbi porttitor lorem id ligula suspendisse ornare consequat", + }, + { + "id": 17, + "name": "volutpat", + "description": "consequat varius integer ac leo pellentesque ultrices mattis odio donec vitae nisi nam ultrices libero non mattis pulvinar nulla pede ullamcorper augue a suscipit nulla elit ac nulla sed vel enim sit amet nunc viverra dapibus nulla suscipit ligula in lacus curabitur at", + }, + { + "id": 18, + "name": "mi", + "description": "ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae donec pharetra magna vestibulum aliquet ultrices erat tortor sollicitudin mi sit amet lobortis sapien sapien non mi integer ac neque duis bibendum morbi non quam nec dui luctus rutrum nulla tellus in sagittis", + }, + { + "id": 19, + "name": "proin", + "description": "elementum nullam varius nulla facilisi cras non velit nec nisi vulputate nonummy maecenas tincidunt lacus at velit vivamus vel nulla eget eros elementum pellentesque quisque porta volutpat erat quisque erat eros viverra eget congue", + }, + { + "id": 20, + "name": "habitasse", + "description": "urna ut tellus nulla ut erat id mauris vulputate elementum nullam varius nulla facilisi cras non velit nec nisi vulputate nonummy maecenas tincidunt lacus at velit vivamus vel nulla", + }, + { + "id": 21, + "name": "ultrices", + "description": "sapien cum sociis natoque penatibus et magnis dis parturient montes nascetur ridiculus mus etiam vel augue vestibulum rutrum rutrum neque aenean auctor gravida sem praesent id massa id nisl venenatis lacinia aenean sit amet justo morbi ut odio cras mi pede", + }, + { + "id": 22, + "name": "ante", + "description": "lobortis sapien sapien non mi integer ac neque duis bibendum morbi non quam nec", + }, + { + "id": 23, + "name": "eget", + "description": "in hac habitasse platea dictumst etiam faucibus cursus urna ut", + }, + { + "id": 24, + "name": "pellentesque", + "description": "blandit mi in porttitor pede justo eu massa donec dapibus duis at velit eu est congue elementum in hac habitasse platea dictumst morbi vestibulum velit id pretium iaculis diam erat fermentum justo nec condimentum neque sapien placerat ante nulla justo aliquam", + }, { - "id": "cfc3w1ypx6g6x81ftygyzhk4ak", - "name": "DMG1012T-7", - "description": "Transistor: N-MOSFET; unipolar; 20V; 0.45A; 0.28W; SOT523", + "id": 25, + "name": "elit", + "description": "vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae mauris viverra diam vitae quam suspendisse potenti nullam porttitor lacus", }, { - "id": "fv5vatbcprgx2910n2h3czc1xv", - "name": "SK-3296S-01-L1", - "description": "SPDT 50mA @ 12VDC 12V On-On SMD Toggle Switches RoHS", + "id": 26, + "name": "tempus", + "description": "cursus urna ut tellus nulla ut erat id mauris vulputate elementum nullam varius nulla facilisi cras non velit nec nisi vulputate nonummy", }, { - "id": "ath9dj6w8pjjf94g2sysfmv3f3", - "name": "AO3400A", - "description": "N-Channel 30V 5.8A 1.4V @ 250uA 28mΩ @ 5.8A,10V 1.4W MOSFET", + "id": 27, + "name": "metus", + "description": "auctor sed tristique in tempus sit amet sem fusce consequat nulla nisl nunc nisl duis bibendum felis sed interdum venenatis turpis enim blandit mi in porttitor pede justo eu massa donec dapibus", }, - ][:limit] + { + "id": 28, + "name": "turpis", + "description": "donec ut mauris eget massa tempor convallis nulla neque libero convallis eget eleifend luctus ultricies eu nibh quisque id justo sit amet sapien dignissim vestibulum vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae nulla dapibus dolor vel est donec odio justo sollicitudin", + }, + { + "id": 29, + "name": "sollicitudin", + "description": "ac nibh fusce lacus purus aliquet at feugiat non pretium quis lectus suspendisse potenti in eleifend quam a odio in hac habitasse platea dictumst maecenas ut massa quis augue luctus tincidunt nulla mollis molestie lorem quisque ut", + }, + { + "id": 30, + "name": "nullam", + "description": "hac habitasse platea dictumst aliquam augue quam sollicitudin vitae consectetuer eget rutrum at lorem integer tincidunt ante vel ipsum praesent blandit lacinia erat vestibulum sed magna at nunc commodo placerat praesent blandit nam nulla integer pede justo lacinia", + }, + { + "id": 31, + "name": "integer", + "description": "sem mauris laoreet ut rhoncus aliquet pulvinar sed nisl nunc rhoncus dui vel sem sed sagittis nam congue risus semper porta volutpat quam pede", + }, + { + "id": 32, + "name": "in", + "description": "turpis a pede posuere nonummy integer non velit donec diam neque vestibulum eget vulputate ut ultrices vel augue vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae", + }, + { + "id": 33, + "name": "volutpat", + "description": "suspendisse accumsan tortor quis turpis sed ante vivamus tortor duis mattis egestas metus aenean fermentum donec ut mauris eget massa tempor convallis nulla neque libero convallis eget eleifend luctus ultricies eu nibh quisque id justo sit amet sapien dignissim vestibulum vestibulum ante ipsum primis", + }, + { + "id": 34, + "name": "id", + "description": "imperdiet nullam orci pede venenatis non sodales sed tincidunt eu felis fusce posuere felis sed lacus morbi sem mauris laoreet ut rhoncus aliquet pulvinar sed nisl nunc rhoncus dui vel sem sed sagittis nam", + }, + { + "id": 35, + "name": "et", + "description": "nulla elit ac nulla sed vel enim sit amet nunc viverra dapibus nulla suscipit ligula in lacus curabitur at ipsum ac tellus", + }, + { + "id": 36, + "name": "augue", + "description": "accumsan odio curabitur convallis duis consequat dui nec nisi volutpat eleifend donec ut dolor morbi vel lectus in quam fringilla rhoncus mauris enim leo rhoncus", + }, + { + "id": 37, + "name": "faucibus", + "description": "arcu sed augue aliquam erat volutpat in congue etiam justo etiam pretium iaculis justo in hac habitasse platea dictumst", + }, + { + "id": 38, + "name": "morbi", + "description": "enim blandit mi in porttitor pede justo eu massa donec dapibus duis at velit eu est congue elementum in hac habitasse platea dictumst morbi vestibulum velit id pretium iaculis diam erat fermentum justo nec condimentum neque sapien placerat ante nulla justo aliquam quis turpis eget", + }, + { + "id": 39, + "name": "leo", + "description": "morbi porttitor lorem id ligula suspendisse ornare consequat lectus in est risus auctor sed tristique in tempus sit amet sem fusce consequat nulla nisl nunc nisl duis bibendum felis sed interdum", + }, + { + "id": 40, + "name": "curae", + "description": "interdum in ante vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae", + }, + { + "id": 41, + "name": "dapibus", + "description": "nulla nisl nunc nisl duis bibendum felis sed interdum venenatis turpis enim blandit mi in porttitor pede justo eu massa donec dapibus duis at velit eu est congue elementum in hac habitasse platea dictumst morbi vestibulum velit id pretium iaculis diam erat fermentum justo nec condimentum", + }, + { + "id": 42, + "name": "duis", + "description": "eu est congue elementum in hac habitasse platea dictumst morbi vestibulum velit id pretium iaculis diam erat fermentum justo nec", + }, + { + "id": 43, + "name": "a", + "description": "eu felis fusce posuere felis sed lacus morbi sem mauris laoreet ut", + }, + { + "id": 44, + "name": "lorem", + "description": "pede malesuada in imperdiet et commodo vulputate justo in blandit ultrices enim lorem ipsum dolor sit amet consectetuer adipiscing elit proin interdum mauris", + }, + { + "id": 45, + "name": "risus", + "description": "amet eros suspendisse accumsan tortor quis turpis sed ante vivamus tortor duis mattis", + }, + { + "id": 46, + "name": "in", + "description": "cursus id turpis integer aliquet massa id lobortis convallis tortor risus dapibus augue vel accumsan tellus nisi eu orci mauris lacinia sapien quis libero nullam sit amet turpis elementum ligula vehicula consequat morbi a ipsum integer a nibh in quis justo maecenas rhoncus aliquam", + }, + { + "id": 47, + "name": "vel", + "description": "feugiat et eros vestibulum ac est lacinia nisi venenatis tristique fusce congue diam id ornare imperdiet sapien urna pretium nisl ut volutpat sapien arcu sed augue aliquam erat volutpat in congue etiam justo etiam pretium iaculis", + }, + { + "id": 48, + "name": "varius", + "description": "felis sed interdum venenatis turpis enim blandit mi in porttitor pede justo eu massa donec", + }, + { + "id": 49, + "name": "vestibulum", + "description": "maecenas tristique est et tempus semper est quam pharetra magna ac consequat metus sapien ut nunc vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia", + }, + { + "id": 50, + "name": "pulvinar", + "description": "nulla sed vel enim sit amet nunc viverra dapibus nulla suscipit ligula in lacus curabitur at ipsum ac tellus semper interdum mauris ullamcorper purus sit amet nulla quisque arcu libero rutrum ac lobortis vel dapibus at diam", + }, + { + "id": 51, + "name": "amet", + "description": "proin risus praesent lectus vestibulum quam sapien varius ut blandit non interdum in ante vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae duis faucibus accumsan odio curabitur convallis duis consequat", + }, + { + "id": 52, + "name": "faucibus", + "description": "sed vel enim sit amet nunc viverra dapibus nulla suscipit ligula in lacus curabitur at ipsum ac tellus semper interdum mauris ullamcorper purus sit amet nulla quisque", + }, + { + "id": 53, + "name": "vulputate", + "description": "sed lacus morbi sem mauris laoreet ut rhoncus aliquet pulvinar sed nisl nunc rhoncus dui vel sem sed sagittis nam congue risus semper porta volutpat quam pede lobortis ligula sit amet eleifend pede libero quis orci nullam molestie nibh in lectus pellentesque at nulla suspendisse potenti cras in purus eu", + }, + { + "id": 54, + "name": "ut", + "description": "lacus purus aliquet at feugiat non pretium quis lectus suspendisse potenti in eleifend quam a odio in hac habitasse platea dictumst maecenas ut massa quis augue luctus tincidunt nulla", + }, + { + "id": 55, + "name": "dui", + "description": "dolor sit amet consectetuer adipiscing elit proin interdum mauris non ligula pellentesque ultrices phasellus id sapien in sapien iaculis congue vivamus metus arcu adipiscing molestie hendrerit at vulputate vitae nisl aenean lectus pellentesque eget", + }, + { + "id": 56, + "name": "sit", + "description": "ac nulla sed vel enim sit amet nunc viverra dapibus nulla suscipit ligula in lacus curabitur at ipsum ac tellus semper interdum mauris ullamcorper purus sit amet nulla quisque arcu libero rutrum ac", + }, + { + "id": 57, + "name": "tellus", + "description": "pellentesque ultrices phasellus id sapien in sapien iaculis congue vivamus metus arcu adipiscing molestie hendrerit at vulputate vitae nisl aenean lectus pellentesque eget nunc donec quis orci eget orci vehicula condimentum curabitur in libero ut massa volutpat convallis morbi odio odio elementum eu interdum eu tincidunt", + }, + { + "id": 58, + "name": "magnis", + "description": "et eros vestibulum ac est lacinia nisi venenatis tristique fusce congue diam id ornare imperdiet sapien urna pretium nisl", + }, + { + "id": 59, + "name": "nisl", + "description": "in hac habitasse platea dictumst morbi vestibulum velit id pretium iaculis diam erat fermentum justo nec condimentum neque sapien placerat ante nulla justo aliquam", + }, + { + "id": 60, + "name": "tortor", + "description": "nulla nisl nunc nisl duis bibendum felis sed interdum venenatis turpis enim blandit", + }, + { + "id": 61, + "name": "luctus", + "description": "risus dapibus augue vel accumsan tellus nisi eu orci mauris lacinia sapien quis libero nullam sit amet turpis elementum ligula vehicula consequat morbi a ipsum integer a nibh in", + }, + { + "id": 62, + "name": "nisi", + "description": "elit proin risus praesent lectus vestibulum quam sapien varius ut blandit non interdum in ante vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae duis faucibus accumsan odio curabitur convallis duis consequat dui nec nisi volutpat eleifend donec", + }, + { + "id": 63, + "name": "velit", + "description": "nulla integer pede justo lacinia eget tincidunt eget tempus vel pede morbi porttitor lorem id ligula suspendisse ornare consequat lectus in est risus auctor sed tristique in tempus sit amet sem fusce consequat", + }, + { + "id": 64, + "name": "mauris", + "description": "duis faucibus accumsan odio curabitur convallis duis consequat dui nec nisi volutpat eleifend donec ut dolor morbi vel lectus in quam fringilla", + }, + { + "id": 65, + "name": "nulla", + "description": "nullam porttitor lacus at turpis donec posuere metus vitae ipsum aliquam non mauris morbi non lectus aliquam sit amet diam in magna", + }, + { + "id": 66, + "name": "rutrum", + "description": "mauris non ligula pellentesque ultrices phasellus id sapien in sapien", + }, + { + "id": 67, + "name": "rhoncus", + "description": "platea dictumst aliquam augue quam sollicitudin vitae consectetuer eget rutrum at lorem integer tincidunt ante vel ipsum praesent", + }, + { + "id": 68, + "name": "odio", + "description": "non mattis pulvinar nulla pede ullamcorper augue a suscipit nulla elit ac nulla sed vel enim sit amet nunc viverra dapibus", + }, + { + "id": 69, + "name": "quis", + "description": "ultrices posuere cubilia curae nulla dapibus dolor vel est donec odio justo sollicitudin ut suscipit a feugiat et eros vestibulum ac est lacinia nisi venenatis tristique fusce congue", + }, + { + "id": 70, + "name": "et", + "description": "vitae nisl aenean lectus pellentesque eget nunc donec quis orci eget orci vehicula condimentum curabitur in libero ut massa volutpat convallis morbi odio odio elementum eu interdum eu tincidunt in leo maecenas pulvinar lobortis est phasellus sit amet erat nulla tempus vivamus", + }, + { + "id": 71, + "name": "enim", + "description": "mauris ullamcorper purus sit amet nulla quisque arcu libero rutrum ac lobortis vel dapibus at diam nam", + }, + { + "id": 72, + "name": "libero", + "description": "risus praesent lectus vestibulum quam sapien varius ut blandit non interdum in ante vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae duis faucibus accumsan odio curabitur convallis duis consequat dui nec nisi", + }, + { + "id": 73, + "name": "vel", + "description": "pede ac diam cras pellentesque volutpat dui maecenas tristique est et tempus semper est quam", + }, + { + "id": 74, + "name": "tortor", + "description": "varius integer ac leo pellentesque ultrices mattis odio donec vitae nisi nam ultrices libero non mattis pulvinar", + }, + { + "id": 75, + "name": "velit", + "description": "nisl nunc nisl duis bibendum felis sed interdum venenatis turpis enim blandit mi in porttitor pede justo eu massa donec dapibus duis at", + }, + { + "id": 76, + "name": "risus", + "description": "dui proin leo odio porttitor id consequat in consequat ut nulla sed accumsan felis ut at dolor quis odio consequat varius integer ac leo pellentesque ultrices mattis odio donec vitae nisi nam ultrices libero non mattis pulvinar nulla", + }, + { + "id": 77, + "name": "diam", + "description": "sem duis aliquam convallis nunc proin at turpis a pede posuere nonummy integer non velit donec diam neque vestibulum eget vulputate ut ultrices vel augue vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae donec pharetra magna vestibulum aliquet ultrices erat tortor sollicitudin mi sit amet", + }, + { + "id": 78, + "name": "eu", + "description": "condimentum neque sapien placerat ante nulla justo aliquam quis turpis eget elit sodales scelerisque mauris sit amet eros suspendisse accumsan tortor quis turpis sed ante vivamus tortor duis mattis egestas metus aenean fermentum donec ut mauris eget massa tempor convallis nulla neque libero convallis eget eleifend luctus", + }, + { + "id": 79, + "name": "primis", + "description": "vitae mattis nibh ligula nec sem duis aliquam convallis nunc proin at turpis a pede posuere nonummy integer non velit", + }, + { + "id": 80, + "name": "nisi", + "description": "etiam faucibus cursus urna ut tellus nulla ut erat id mauris vulputate elementum nullam varius nulla facilisi cras non velit nec nisi vulputate nonummy maecenas tincidunt lacus at velit vivamus vel nulla eget eros elementum pellentesque quisque porta volutpat erat", + }, + { + "id": 81, + "name": "elit", + "description": "ut rhoncus aliquet pulvinar sed nisl nunc rhoncus dui vel sem sed sagittis nam congue risus semper porta volutpat quam pede lobortis ligula sit amet eleifend pede libero quis orci nullam molestie nibh in lectus pellentesque at", + }, + { + "id": 82, + "name": "sed", + "description": "vel ipsum praesent blandit lacinia erat vestibulum sed magna at nunc commodo placerat praesent blandit nam nulla integer pede justo lacinia eget tincidunt eget tempus vel pede morbi porttitor lorem id ligula suspendisse ornare consequat lectus in", + }, + { + "id": 83, + "name": "sapien", + "description": "eu felis fusce posuere felis sed lacus morbi sem mauris laoreet", + }, + { + "id": 84, + "name": "posuere", + "description": "fringilla rhoncus mauris enim leo rhoncus sed vestibulum sit amet cursus id turpis integer", + }, + { + "id": 85, + "name": "dapibus", + "description": "libero non mattis pulvinar nulla pede ullamcorper augue a suscipit nulla elit", + }, + { + "id": 86, + "name": "tortor", + "description": "lectus pellentesque eget nunc donec quis orci eget orci vehicula condimentum curabitur in libero ut massa volutpat convallis morbi odio odio elementum eu interdum eu tincidunt in leo maecenas pulvinar lobortis est phasellus sit amet erat nulla tempus vivamus in felis eu", + }, + { + "id": 87, + "name": "ultrices", + "description": "sapien iaculis congue vivamus metus arcu adipiscing molestie hendrerit at vulputate vitae nisl aenean lectus pellentesque eget nunc donec quis orci eget orci vehicula condimentum curabitur in libero ut massa volutpat convallis morbi odio odio elementum eu interdum eu tincidunt in", + }, + { + "id": 88, + "name": "in", + "description": "pellentesque at nulla suspendisse potenti cras in purus eu magna vulputate", + }, + { + "id": 89, + "name": "ut", + "description": "nunc donec quis orci eget orci vehicula condimentum curabitur in libero ut massa volutpat convallis morbi odio odio elementum eu interdum eu tincidunt in leo maecenas pulvinar lobortis est phasellus sit amet erat nulla tempus vivamus in felis eu sapien cursus vestibulum proin eu mi nulla ac enim", + }, + { + "id": 90, + "name": "faucibus", + "description": "a ipsum integer a nibh in quis justo maecenas rhoncus aliquam lacus morbi quis tortor id nulla ultrices aliquet maecenas leo odio condimentum id luctus nec", + }, + { + "id": 91, + "name": "risus", + "description": "elementum in hac habitasse platea dictumst morbi vestibulum velit id pretium iaculis diam erat fermentum justo nec condimentum neque sapien placerat ante nulla justo aliquam quis turpis eget elit sodales scelerisque mauris sit amet eros suspendisse accumsan tortor quis turpis sed ante vivamus tortor", + }, + { + "id": 92, + "name": "in", + "description": "ut massa volutpat convallis morbi odio odio elementum eu interdum eu tincidunt in leo maecenas pulvinar lobortis est", + }, + { + "id": 93, + "name": "platea", + "description": "pharetra magna vestibulum aliquet ultrices erat tortor sollicitudin mi sit amet lobortis sapien sapien non mi integer", + }, + { + "id": 94, + "name": "integer", + "description": "orci eget orci vehicula condimentum curabitur in libero ut massa volutpat", + }, + { + "id": 95, + "name": "dui", + "description": "curabitur at ipsum ac tellus semper interdum mauris ullamcorper purus sit amet nulla quisque arcu libero rutrum ac lobortis vel dapibus at diam nam tristique tortor eu pede", + }, + { + "id": 96, + "name": "facilisi", + "description": "eleifend donec ut dolor morbi vel lectus in quam fringilla rhoncus mauris enim leo rhoncus sed vestibulum sit amet cursus id turpis integer aliquet massa id lobortis convallis tortor", + }, + { + "id": 97, + "name": "dictumst", + "description": "quam pede lobortis ligula sit amet eleifend pede libero quis orci nullam molestie nibh in lectus pellentesque at nulla suspendisse potenti cras in purus eu magna vulputate luctus cum sociis natoque penatibus et magnis dis parturient montes nascetur ridiculus mus vivamus vestibulum sagittis", + }, + { + "id": 98, + "name": "vel", + "description": "blandit mi in porttitor pede justo eu massa donec dapibus duis at velit eu est congue elementum", + }, + { + "id": 99, + "name": "augue", + "description": "pede justo lacinia eget tincidunt eget tempus vel pede morbi porttitor lorem id ligula suspendisse ornare consequat lectus in est risus auctor sed tristique in tempus sit amet sem fusce consequat nulla nisl nunc nisl duis bibendum felis sed interdum venenatis turpis enim blandit mi in porttitor", + }, + { + "id": 100, + "name": "praesent", + "description": "at lorem integer tincidunt ante vel ipsum praesent blandit lacinia erat vestibulum sed magna at", + }, + ] + + return data[start : start + limit] # noqa: E203 diff --git a/app/services/project_service.py b/app/services/project_service.py index 5e1febd..8e2b98e 100644 --- a/app/services/project_service.py +++ b/app/services/project_service.py @@ -1,25 +1,518 @@ from typing import List -def get_project_count() -> int: +async def get_project_count() -> int: return 34 -def get_latest_projects(limit: int = 5) -> List: - return [ +async def get_latest_projects( + start: int = 0, limit: int = 5 +) -> List: # TODO: change to List[Project] after db implementation + + start = max(0, start) + limit = max(0, limit) + + data = [ + { + "id": 1, + "name": "felis", + "description": "feugiat non pretium quis lectus suspendisse potenti in eleifend quam a odio in hac habitasse platea dictumst maecenas ut massa quis augue luctus tincidunt nulla mollis molestie lorem quisque ut erat", + }, + { + "id": 2, + "name": "adipiscing", + "description": "praesent blandit nam nulla integer pede justo lacinia eget tincidunt eget tempus vel pede morbi porttitor lorem id ligula suspendisse ornare consequat lectus in est risus", + }, + { + "id": 3, + "name": "augue", + "description": "leo maecenas pulvinar lobortis est phasellus sit amet erat nulla tempus vivamus in felis eu sapien cursus vestibulum proin eu mi nulla ac enim in tempor turpis nec euismod scelerisque quam turpis adipiscing lorem vitae mattis nibh ligula nec sem duis aliquam convallis nunc proin at turpis a pede posuere", + }, + { + "id": 4, + "name": "vulputate", + "description": "amet lobortis sapien sapien non mi integer ac neque duis bibendum morbi non quam nec dui luctus rutrum nulla tellus in sagittis dui vel nisl duis ac nibh fusce lacus purus aliquet at feugiat non pretium quis lectus suspendisse potenti in", + }, + { + "id": 5, + "name": "vestibulum", + "description": "porttitor id consequat in consequat ut nulla sed accumsan felis ut at dolor quis odio consequat varius integer ac", + }, + { + "id": 6, + "name": "massa", + "description": "lectus aliquam sit amet diam in magna bibendum imperdiet nullam orci pede venenatis non sodales sed tincidunt eu felis fusce posuere felis sed lacus morbi sem mauris laoreet ut rhoncus aliquet pulvinar sed nisl nunc rhoncus dui vel sem", + }, + { + "id": 7, + "name": "fusce", + "description": "faucibus cursus urna ut tellus nulla ut erat id mauris vulputate elementum nullam varius nulla facilisi cras non velit nec nisi vulputate nonummy maecenas tincidunt lacus at velit vivamus vel nulla eget eros", + }, + { + "id": 8, + "name": "non", + "description": "turpis a pede posuere nonummy integer non velit donec diam neque vestibulum eget vulputate ut ultrices vel augue vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae donec pharetra magna", + }, + { + "id": 9, + "name": "turpis", + "description": "nulla nisl nunc nisl duis bibendum felis sed interdum venenatis turpis enim blandit mi in porttitor pede justo eu massa donec dapibus duis at velit eu est congue elementum in hac habitasse", + }, + { + "id": 10, + "name": "eu", + "description": "in sapien iaculis congue vivamus metus arcu adipiscing molestie hendrerit at vulputate vitae nisl aenean lectus pellentesque eget nunc donec quis", + }, + { + "id": 11, + "name": "est", + "description": "molestie hendrerit at vulputate vitae nisl aenean lectus pellentesque eget nunc donec quis orci eget orci vehicula condimentum curabitur in libero ut massa volutpat convallis morbi odio odio elementum eu interdum eu tincidunt in leo maecenas pulvinar lobortis est phasellus sit amet", + }, + { + "id": 12, + "name": "vestibulum", + "description": "suspendisse potenti cras in purus eu magna vulputate luctus cum sociis natoque penatibus et magnis dis parturient montes", + }, + { + "id": 13, + "name": "turpis", + "description": "turpis elementum ligula vehicula consequat morbi a ipsum integer a nibh in quis justo maecenas rhoncus aliquam lacus morbi quis tortor id nulla ultrices aliquet maecenas leo odio condimentum id luctus nec molestie sed justo pellentesque viverra pede ac diam cras pellentesque volutpat dui", + }, + { + "id": 14, + "name": "cum", + "description": "vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae mauris viverra diam vitae quam suspendisse potenti nullam porttitor lacus at turpis donec posuere metus vitae ipsum aliquam non mauris morbi non lectus aliquam sit amet diam in magna bibendum imperdiet", + }, + { + "id": 15, + "name": "nulla", + "description": "id nulla ultrices aliquet maecenas leo odio condimentum id luctus nec molestie sed justo pellentesque viverra pede ac diam cras pellentesque volutpat dui maecenas tristique est et tempus semper est quam pharetra magna ac consequat metus sapien ut nunc vestibulum ante ipsum", + }, + { + "id": 16, + "name": "justo", + "description": "tempus vel pede morbi porttitor lorem id ligula suspendisse ornare consequat", + }, + { + "id": 17, + "name": "volutpat", + "description": "consequat varius integer ac leo pellentesque ultrices mattis odio donec vitae nisi nam ultrices libero non mattis pulvinar nulla pede ullamcorper augue a suscipit nulla elit ac nulla sed vel enim sit amet nunc viverra dapibus nulla suscipit ligula in lacus curabitur at", + }, + { + "id": 18, + "name": "mi", + "description": "ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae donec pharetra magna vestibulum aliquet ultrices erat tortor sollicitudin mi sit amet lobortis sapien sapien non mi integer ac neque duis bibendum morbi non quam nec dui luctus rutrum nulla tellus in sagittis", + }, + { + "id": 19, + "name": "proin", + "description": "elementum nullam varius nulla facilisi cras non velit nec nisi vulputate nonummy maecenas tincidunt lacus at velit vivamus vel nulla eget eros elementum pellentesque quisque porta volutpat erat quisque erat eros viverra eget congue", + }, + { + "id": 20, + "name": "habitasse", + "description": "urna ut tellus nulla ut erat id mauris vulputate elementum nullam varius nulla facilisi cras non velit nec nisi vulputate nonummy maecenas tincidunt lacus at velit vivamus vel nulla", + }, + { + "id": 21, + "name": "ultrices", + "description": "sapien cum sociis natoque penatibus et magnis dis parturient montes nascetur ridiculus mus etiam vel augue vestibulum rutrum rutrum neque aenean auctor gravida sem praesent id massa id nisl venenatis lacinia aenean sit amet justo morbi ut odio cras mi pede", + }, + { + "id": 22, + "name": "ante", + "description": "lobortis sapien sapien non mi integer ac neque duis bibendum morbi non quam nec", + }, + { + "id": 23, + "name": "eget", + "description": "in hac habitasse platea dictumst etiam faucibus cursus urna ut", + }, + { + "id": 24, + "name": "pellentesque", + "description": "blandit mi in porttitor pede justo eu massa donec dapibus duis at velit eu est congue elementum in hac habitasse platea dictumst morbi vestibulum velit id pretium iaculis diam erat fermentum justo nec condimentum neque sapien placerat ante nulla justo aliquam", + }, { - "id": "5z2v341ypg4f6b684n4gra3myk", - "name": "STR-16-011-BatteryDesulfator", - "description": "Battery Desulfator", + "id": 25, + "name": "elit", + "description": "vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae mauris viverra diam vitae quam suspendisse potenti nullam porttitor lacus", }, { - "id": "5hhxy81gm44f69rn3mkpz8c6tn", - "name": "Dog Bark Stop", - "description": "Project to stop dog barking", + "id": 26, + "name": "tempus", + "description": "cursus urna ut tellus nulla ut erat id mauris vulputate elementum nullam varius nulla facilisi cras non velit nec nisi vulputate nonummy", }, { - "id": "e77wqg1h364f69rn3mkpz8c6tn", - "name": "STR-15-011-PiCluster", - "description": "Raspberry Pi Cluster", + "id": 27, + "name": "metus", + "description": "auctor sed tristique in tempus sit amet sem fusce consequat nulla nisl nunc nisl duis bibendum felis sed interdum venenatis turpis enim blandit mi in porttitor pede justo eu massa donec dapibus", }, - ][:limit] + { + "id": 28, + "name": "turpis", + "description": "donec ut mauris eget massa tempor convallis nulla neque libero convallis eget eleifend luctus ultricies eu nibh quisque id justo sit amet sapien dignissim vestibulum vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae nulla dapibus dolor vel est donec odio justo sollicitudin", + }, + { + "id": 29, + "name": "sollicitudin", + "description": "ac nibh fusce lacus purus aliquet at feugiat non pretium quis lectus suspendisse potenti in eleifend quam a odio in hac habitasse platea dictumst maecenas ut massa quis augue luctus tincidunt nulla mollis molestie lorem quisque ut", + }, + { + "id": 30, + "name": "nullam", + "description": "hac habitasse platea dictumst aliquam augue quam sollicitudin vitae consectetuer eget rutrum at lorem integer tincidunt ante vel ipsum praesent blandit lacinia erat vestibulum sed magna at nunc commodo placerat praesent blandit nam nulla integer pede justo lacinia", + }, + { + "id": 31, + "name": "integer", + "description": "sem mauris laoreet ut rhoncus aliquet pulvinar sed nisl nunc rhoncus dui vel sem sed sagittis nam congue risus semper porta volutpat quam pede", + }, + { + "id": 32, + "name": "in", + "description": "turpis a pede posuere nonummy integer non velit donec diam neque vestibulum eget vulputate ut ultrices vel augue vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae", + }, + { + "id": 33, + "name": "volutpat", + "description": "suspendisse accumsan tortor quis turpis sed ante vivamus tortor duis mattis egestas metus aenean fermentum donec ut mauris eget massa tempor convallis nulla neque libero convallis eget eleifend luctus ultricies eu nibh quisque id justo sit amet sapien dignissim vestibulum vestibulum ante ipsum primis", + }, + { + "id": 34, + "name": "id", + "description": "imperdiet nullam orci pede venenatis non sodales sed tincidunt eu felis fusce posuere felis sed lacus morbi sem mauris laoreet ut rhoncus aliquet pulvinar sed nisl nunc rhoncus dui vel sem sed sagittis nam", + }, + { + "id": 35, + "name": "et", + "description": "nulla elit ac nulla sed vel enim sit amet nunc viverra dapibus nulla suscipit ligula in lacus curabitur at ipsum ac tellus", + }, + { + "id": 36, + "name": "augue", + "description": "accumsan odio curabitur convallis duis consequat dui nec nisi volutpat eleifend donec ut dolor morbi vel lectus in quam fringilla rhoncus mauris enim leo rhoncus", + }, + { + "id": 37, + "name": "faucibus", + "description": "arcu sed augue aliquam erat volutpat in congue etiam justo etiam pretium iaculis justo in hac habitasse platea dictumst", + }, + { + "id": 38, + "name": "morbi", + "description": "enim blandit mi in porttitor pede justo eu massa donec dapibus duis at velit eu est congue elementum in hac habitasse platea dictumst morbi vestibulum velit id pretium iaculis diam erat fermentum justo nec condimentum neque sapien placerat ante nulla justo aliquam quis turpis eget", + }, + { + "id": 39, + "name": "leo", + "description": "morbi porttitor lorem id ligula suspendisse ornare consequat lectus in est risus auctor sed tristique in tempus sit amet sem fusce consequat nulla nisl nunc nisl duis bibendum felis sed interdum", + }, + { + "id": 40, + "name": "curae", + "description": "interdum in ante vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae", + }, + { + "id": 41, + "name": "dapibus", + "description": "nulla nisl nunc nisl duis bibendum felis sed interdum venenatis turpis enim blandit mi in porttitor pede justo eu massa donec dapibus duis at velit eu est congue elementum in hac habitasse platea dictumst morbi vestibulum velit id pretium iaculis diam erat fermentum justo nec condimentum", + }, + { + "id": 42, + "name": "duis", + "description": "eu est congue elementum in hac habitasse platea dictumst morbi vestibulum velit id pretium iaculis diam erat fermentum justo nec", + }, + { + "id": 43, + "name": "a", + "description": "eu felis fusce posuere felis sed lacus morbi sem mauris laoreet ut", + }, + { + "id": 44, + "name": "lorem", + "description": "pede malesuada in imperdiet et commodo vulputate justo in blandit ultrices enim lorem ipsum dolor sit amet consectetuer adipiscing elit proin interdum mauris", + }, + { + "id": 45, + "name": "risus", + "description": "amet eros suspendisse accumsan tortor quis turpis sed ante vivamus tortor duis mattis", + }, + { + "id": 46, + "name": "in", + "description": "cursus id turpis integer aliquet massa id lobortis convallis tortor risus dapibus augue vel accumsan tellus nisi eu orci mauris lacinia sapien quis libero nullam sit amet turpis elementum ligula vehicula consequat morbi a ipsum integer a nibh in quis justo maecenas rhoncus aliquam", + }, + { + "id": 47, + "name": "vel", + "description": "feugiat et eros vestibulum ac est lacinia nisi venenatis tristique fusce congue diam id ornare imperdiet sapien urna pretium nisl ut volutpat sapien arcu sed augue aliquam erat volutpat in congue etiam justo etiam pretium iaculis", + }, + { + "id": 48, + "name": "varius", + "description": "felis sed interdum venenatis turpis enim blandit mi in porttitor pede justo eu massa donec", + }, + { + "id": 49, + "name": "vestibulum", + "description": "maecenas tristique est et tempus semper est quam pharetra magna ac consequat metus sapien ut nunc vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia", + }, + { + "id": 50, + "name": "pulvinar", + "description": "nulla sed vel enim sit amet nunc viverra dapibus nulla suscipit ligula in lacus curabitur at ipsum ac tellus semper interdum mauris ullamcorper purus sit amet nulla quisque arcu libero rutrum ac lobortis vel dapibus at diam", + }, + { + "id": 51, + "name": "amet", + "description": "proin risus praesent lectus vestibulum quam sapien varius ut blandit non interdum in ante vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae duis faucibus accumsan odio curabitur convallis duis consequat", + }, + { + "id": 52, + "name": "faucibus", + "description": "sed vel enim sit amet nunc viverra dapibus nulla suscipit ligula in lacus curabitur at ipsum ac tellus semper interdum mauris ullamcorper purus sit amet nulla quisque", + }, + { + "id": 53, + "name": "vulputate", + "description": "sed lacus morbi sem mauris laoreet ut rhoncus aliquet pulvinar sed nisl nunc rhoncus dui vel sem sed sagittis nam congue risus semper porta volutpat quam pede lobortis ligula sit amet eleifend pede libero quis orci nullam molestie nibh in lectus pellentesque at nulla suspendisse potenti cras in purus eu", + }, + { + "id": 54, + "name": "ut", + "description": "lacus purus aliquet at feugiat non pretium quis lectus suspendisse potenti in eleifend quam a odio in hac habitasse platea dictumst maecenas ut massa quis augue luctus tincidunt nulla", + }, + { + "id": 55, + "name": "dui", + "description": "dolor sit amet consectetuer adipiscing elit proin interdum mauris non ligula pellentesque ultrices phasellus id sapien in sapien iaculis congue vivamus metus arcu adipiscing molestie hendrerit at vulputate vitae nisl aenean lectus pellentesque eget", + }, + { + "id": 56, + "name": "sit", + "description": "ac nulla sed vel enim sit amet nunc viverra dapibus nulla suscipit ligula in lacus curabitur at ipsum ac tellus semper interdum mauris ullamcorper purus sit amet nulla quisque arcu libero rutrum ac", + }, + { + "id": 57, + "name": "tellus", + "description": "pellentesque ultrices phasellus id sapien in sapien iaculis congue vivamus metus arcu adipiscing molestie hendrerit at vulputate vitae nisl aenean lectus pellentesque eget nunc donec quis orci eget orci vehicula condimentum curabitur in libero ut massa volutpat convallis morbi odio odio elementum eu interdum eu tincidunt", + }, + { + "id": 58, + "name": "magnis", + "description": "et eros vestibulum ac est lacinia nisi venenatis tristique fusce congue diam id ornare imperdiet sapien urna pretium nisl", + }, + { + "id": 59, + "name": "nisl", + "description": "in hac habitasse platea dictumst morbi vestibulum velit id pretium iaculis diam erat fermentum justo nec condimentum neque sapien placerat ante nulla justo aliquam", + }, + { + "id": 60, + "name": "tortor", + "description": "nulla nisl nunc nisl duis bibendum felis sed interdum venenatis turpis enim blandit", + }, + { + "id": 61, + "name": "luctus", + "description": "risus dapibus augue vel accumsan tellus nisi eu orci mauris lacinia sapien quis libero nullam sit amet turpis elementum ligula vehicula consequat morbi a ipsum integer a nibh in", + }, + { + "id": 62, + "name": "nisi", + "description": "elit proin risus praesent lectus vestibulum quam sapien varius ut blandit non interdum in ante vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae duis faucibus accumsan odio curabitur convallis duis consequat dui nec nisi volutpat eleifend donec", + }, + { + "id": 63, + "name": "velit", + "description": "nulla integer pede justo lacinia eget tincidunt eget tempus vel pede morbi porttitor lorem id ligula suspendisse ornare consequat lectus in est risus auctor sed tristique in tempus sit amet sem fusce consequat", + }, + { + "id": 64, + "name": "mauris", + "description": "duis faucibus accumsan odio curabitur convallis duis consequat dui nec nisi volutpat eleifend donec ut dolor morbi vel lectus in quam fringilla", + }, + { + "id": 65, + "name": "nulla", + "description": "nullam porttitor lacus at turpis donec posuere metus vitae ipsum aliquam non mauris morbi non lectus aliquam sit amet diam in magna", + }, + { + "id": 66, + "name": "rutrum", + "description": "mauris non ligula pellentesque ultrices phasellus id sapien in sapien", + }, + { + "id": 67, + "name": "rhoncus", + "description": "platea dictumst aliquam augue quam sollicitudin vitae consectetuer eget rutrum at lorem integer tincidunt ante vel ipsum praesent", + }, + { + "id": 68, + "name": "odio", + "description": "non mattis pulvinar nulla pede ullamcorper augue a suscipit nulla elit ac nulla sed vel enim sit amet nunc viverra dapibus", + }, + { + "id": 69, + "name": "quis", + "description": "ultrices posuere cubilia curae nulla dapibus dolor vel est donec odio justo sollicitudin ut suscipit a feugiat et eros vestibulum ac est lacinia nisi venenatis tristique fusce congue", + }, + { + "id": 70, + "name": "et", + "description": "vitae nisl aenean lectus pellentesque eget nunc donec quis orci eget orci vehicula condimentum curabitur in libero ut massa volutpat convallis morbi odio odio elementum eu interdum eu tincidunt in leo maecenas pulvinar lobortis est phasellus sit amet erat nulla tempus vivamus", + }, + { + "id": 71, + "name": "enim", + "description": "mauris ullamcorper purus sit amet nulla quisque arcu libero rutrum ac lobortis vel dapibus at diam nam", + }, + { + "id": 72, + "name": "libero", + "description": "risus praesent lectus vestibulum quam sapien varius ut blandit non interdum in ante vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae duis faucibus accumsan odio curabitur convallis duis consequat dui nec nisi", + }, + { + "id": 73, + "name": "vel", + "description": "pede ac diam cras pellentesque volutpat dui maecenas tristique est et tempus semper est quam", + }, + { + "id": 74, + "name": "tortor", + "description": "varius integer ac leo pellentesque ultrices mattis odio donec vitae nisi nam ultrices libero non mattis pulvinar", + }, + { + "id": 75, + "name": "velit", + "description": "nisl nunc nisl duis bibendum felis sed interdum venenatis turpis enim blandit mi in porttitor pede justo eu massa donec dapibus duis at", + }, + { + "id": 76, + "name": "risus", + "description": "dui proin leo odio porttitor id consequat in consequat ut nulla sed accumsan felis ut at dolor quis odio consequat varius integer ac leo pellentesque ultrices mattis odio donec vitae nisi nam ultrices libero non mattis pulvinar nulla", + }, + { + "id": 77, + "name": "diam", + "description": "sem duis aliquam convallis nunc proin at turpis a pede posuere nonummy integer non velit donec diam neque vestibulum eget vulputate ut ultrices vel augue vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae donec pharetra magna vestibulum aliquet ultrices erat tortor sollicitudin mi sit amet", + }, + { + "id": 78, + "name": "eu", + "description": "condimentum neque sapien placerat ante nulla justo aliquam quis turpis eget elit sodales scelerisque mauris sit amet eros suspendisse accumsan tortor quis turpis sed ante vivamus tortor duis mattis egestas metus aenean fermentum donec ut mauris eget massa tempor convallis nulla neque libero convallis eget eleifend luctus", + }, + { + "id": 79, + "name": "primis", + "description": "vitae mattis nibh ligula nec sem duis aliquam convallis nunc proin at turpis a pede posuere nonummy integer non velit", + }, + { + "id": 80, + "name": "nisi", + "description": "etiam faucibus cursus urna ut tellus nulla ut erat id mauris vulputate elementum nullam varius nulla facilisi cras non velit nec nisi vulputate nonummy maecenas tincidunt lacus at velit vivamus vel nulla eget eros elementum pellentesque quisque porta volutpat erat", + }, + { + "id": 81, + "name": "elit", + "description": "ut rhoncus aliquet pulvinar sed nisl nunc rhoncus dui vel sem sed sagittis nam congue risus semper porta volutpat quam pede lobortis ligula sit amet eleifend pede libero quis orci nullam molestie nibh in lectus pellentesque at", + }, + { + "id": 82, + "name": "sed", + "description": "vel ipsum praesent blandit lacinia erat vestibulum sed magna at nunc commodo placerat praesent blandit nam nulla integer pede justo lacinia eget tincidunt eget tempus vel pede morbi porttitor lorem id ligula suspendisse ornare consequat lectus in", + }, + { + "id": 83, + "name": "sapien", + "description": "eu felis fusce posuere felis sed lacus morbi sem mauris laoreet", + }, + { + "id": 84, + "name": "posuere", + "description": "fringilla rhoncus mauris enim leo rhoncus sed vestibulum sit amet cursus id turpis integer", + }, + { + "id": 85, + "name": "dapibus", + "description": "libero non mattis pulvinar nulla pede ullamcorper augue a suscipit nulla elit", + }, + { + "id": 86, + "name": "tortor", + "description": "lectus pellentesque eget nunc donec quis orci eget orci vehicula condimentum curabitur in libero ut massa volutpat convallis morbi odio odio elementum eu interdum eu tincidunt in leo maecenas pulvinar lobortis est phasellus sit amet erat nulla tempus vivamus in felis eu", + }, + { + "id": 87, + "name": "ultrices", + "description": "sapien iaculis congue vivamus metus arcu adipiscing molestie hendrerit at vulputate vitae nisl aenean lectus pellentesque eget nunc donec quis orci eget orci vehicula condimentum curabitur in libero ut massa volutpat convallis morbi odio odio elementum eu interdum eu tincidunt in", + }, + { + "id": 88, + "name": "in", + "description": "pellentesque at nulla suspendisse potenti cras in purus eu magna vulputate", + }, + { + "id": 89, + "name": "ut", + "description": "nunc donec quis orci eget orci vehicula condimentum curabitur in libero ut massa volutpat convallis morbi odio odio elementum eu interdum eu tincidunt in leo maecenas pulvinar lobortis est phasellus sit amet erat nulla tempus vivamus in felis eu sapien cursus vestibulum proin eu mi nulla ac enim", + }, + { + "id": 90, + "name": "faucibus", + "description": "a ipsum integer a nibh in quis justo maecenas rhoncus aliquam lacus morbi quis tortor id nulla ultrices aliquet maecenas leo odio condimentum id luctus nec", + }, + { + "id": 91, + "name": "risus", + "description": "elementum in hac habitasse platea dictumst morbi vestibulum velit id pretium iaculis diam erat fermentum justo nec condimentum neque sapien placerat ante nulla justo aliquam quis turpis eget elit sodales scelerisque mauris sit amet eros suspendisse accumsan tortor quis turpis sed ante vivamus tortor", + }, + { + "id": 92, + "name": "in", + "description": "ut massa volutpat convallis morbi odio odio elementum eu interdum eu tincidunt in leo maecenas pulvinar lobortis est", + }, + { + "id": 93, + "name": "platea", + "description": "pharetra magna vestibulum aliquet ultrices erat tortor sollicitudin mi sit amet lobortis sapien sapien non mi integer", + }, + { + "id": 94, + "name": "integer", + "description": "orci eget orci vehicula condimentum curabitur in libero ut massa volutpat", + }, + { + "id": 95, + "name": "dui", + "description": "curabitur at ipsum ac tellus semper interdum mauris ullamcorper purus sit amet nulla quisque arcu libero rutrum ac lobortis vel dapibus at diam nam tristique tortor eu pede", + }, + { + "id": 96, + "name": "facilisi", + "description": "eleifend donec ut dolor morbi vel lectus in quam fringilla rhoncus mauris enim leo rhoncus sed vestibulum sit amet cursus id turpis integer aliquet massa id lobortis convallis tortor", + }, + { + "id": 97, + "name": "dictumst", + "description": "quam pede lobortis ligula sit amet eleifend pede libero quis orci nullam molestie nibh in lectus pellentesque at nulla suspendisse potenti cras in purus eu magna vulputate luctus cum sociis natoque penatibus et magnis dis parturient montes nascetur ridiculus mus vivamus vestibulum sagittis", + }, + { + "id": 98, + "name": "vel", + "description": "blandit mi in porttitor pede justo eu massa donec dapibus duis at velit eu est congue elementum", + }, + { + "id": 99, + "name": "augue", + "description": "pede justo lacinia eget tincidunt eget tempus vel pede morbi porttitor lorem id ligula suspendisse ornare consequat lectus in est risus auctor sed tristique in tempus sit amet sem fusce consequat nulla nisl nunc nisl duis bibendum felis sed interdum venenatis turpis enim blandit mi in porttitor", + }, + { + "id": 100, + "name": "praesent", + "description": "at lorem integer tincidunt ante vel ipsum praesent blandit lacinia erat vestibulum sed magna at", + }, + ] + + return data[start:limit] diff --git a/app/services/storage_service.py b/app/services/storage_service.py index e4d3163..f708e21 100644 --- a/app/services/storage_service.py +++ b/app/services/storage_service.py @@ -1,6 +1,6 @@ -def get_location_count() -> int: +async def get_location_count() -> int: return 234 -def get_locations_used() -> int: +async def get_locations_used() -> int: return 230 diff --git a/app/static/css/app.css b/app/static/css/app.css new file mode 100644 index 0000000..2e34407 --- /dev/null +++ b/app/static/css/app.css @@ -0,0 +1,6 @@ +table td { + max-width:50px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} \ No newline at end of file diff --git a/app/templates/parts/partslist.pt b/app/templates/parts/partslist.pt index ff1c85a..820898c 100644 --- a/app/templates/parts/partslist.pt +++ b/app/templates/parts/partslist.pt @@ -12,543 +12,23 @@
- +
- - - - - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - - - - - - + +
NamePositionOfficeExtnStart dateSalaryPartDescription
1Tiger NixonSystem ArchitectEdinburgh612011/04/25$320,800
2Garrett WintersAccountantTokyo632011/07/25$170,750
3Ashton CoxJunior Technical AuthorSan Francisco662009/01/12$86,000
4Cedric KellySenior Javascript DeveloperEdinburgh222012/03/29$433,060
5Airi SatouAccountantTokyo332008/11/28$162,700
6Brielle WilliamsonIntegration SpecialistNew York612012/12/02$372,000
7Herrod ChandlerSales AssistantSan Francisco592012/08/06$137,500
8Rhona DavidsonIntegration SpecialistTokyo552010/10/14$327,900
9Colleen HurstJavascript DeveloperSan Francisco392009/09/15$205,500
10Sonya FrostSoftware EngineerEdinburgh232008/12/13$103,600
11Jena GainesOffice ManagerLondon302008/12/19$90,560
12Quinn FlynnSupport LeadEdinburgh222013/03/03$342,000
13Charde MarshallRegional DirectorSan Francisco362008/10/16$470,600
14Haley KennedySenior Marketing DesignerLondon432012/12/18$313,500
15Tatyana FitzpatrickRegional DirectorLondon192010/03/17$385,750
16Michael SilvaMarketing DesignerLondon662012/11/27$198,500
17Paul ByrdChief Financial Officer (CFO)New York642010/06/09$725,000
18Gloria LittleSystems AdministratorNew York592009/04/10$237,500
19Bradley GreerSoftware EngineerLondon412012/10/13$132,000
20Dai RiosPersonnel LeadEdinburgh352012/09/26$217,500
21Jenette CaldwellDevelopment LeadNew York302011/09/03$345,000
22Yuri BerryChief Marketing Officer (CMO)New York402009/06/25$675,000
23Caesar VancePre-Sales SupportNew York212011/12/12$106,450
24Doris WilderSales AssistantSidney232010/09/20$85,600
25Angelica RamosChief Executive Officer (CEO)London472009/10/09$1,200,000
26Gavin JoyceDeveloperEdinburgh422010/12/22$92,575
27Jennifer ChangRegional DirectorSingapore282010/11/14$357,650
28Brenden WagnerSoftware EngineerSan Francisco282011/06/07$206,850
29Fiona GreenChief Operating Officer (COO)San Francisco482010/03/11$850,000
30Shou ItouRegional MarketingTokyo202011/08/14$163,000
31Michelle HouseIntegration SpecialistSidney372011/06/02$95,400
32Suki BurksDeveloperLondon532009/10/22$114,500
33Prescott BartlettTechnical AuthorLondon272011/05/07$145,000
34Gavin CortezTeam LeaderSan Francisco222008/10/26$235,500
35Martena MccrayPost-Sales supportEdinburgh462011/03/09$324,050
36Unity ButlerMarketing DesignerSan Francisco472009/12/09$85,675
37Howard HatfieldOffice ManagerSan Francisco512008/12/16$164,500
38Hope FuentesSecretarySan Francisco412010/02/12$109,850
39Vivian HarrellFinancial ControllerSan Francisco622009/02/14$452,500
40Timothy MooneyOffice ManagerLondon372008/12/11$136,200
41Jackson BradshawDirectorNew York652008/09/26$645,750
42Olivia LiangSupport EngineerSingapore642011/02/03$234,500
43Bruno NashSoftware EngineerLondon382011/05/03$163,500
44Sakura YamamotoSupport EngineerTokyo372009/08/19$139,575
45Thor WaltonDeveloperNew York612013/08/11$98,540
46Finn CamachoSupport EngineerSan Francisco472009/07/07$87,500
47Serge BaldwinData CoordinatorSingapore642012/04/09$138,575
48Zenaida FrankSoftware EngineerNew York632010/01/04$125,250
49Zorita SerranoSoftware EngineerSan Francisco562012/06/01$115,000
50Jennifer AcostaJunior Javascript DeveloperEdinburgh432013/02/01$75,650
51Cara StevensSales AssistantNew York462011/12/06$145,600
52Hermione ButlerRegional DirectorLondon472011/03/21$356,250
53Lael GreerSystems AdministratorLondon212009/02/27$103,500
54Jonas AlexanderDeveloperSan Francisco302010/07/14$86,500
55Shad DeckerRegional DirectorEdinburgh512008/11/13$183,000
56Michael BruceJavascript DeveloperSingapore292011/06/27$183,000
57Donna SniderCustomer SupportNew York272011/01/25$112,000
NamePositionOfficeAgeStart dateSalaryPartDescription
@@ -570,18 +50,40 @@ 'columnDefs': [ { 'targets': 0, + 'checkboxes': { 'selectRow': true } - } + }, ], + + + 'select': { 'style': 'multi' }, - "lengthMenu": [[30, 50, 100, -1], [30, 50, 100, "All"]], + "lengthMenu": [[10, 30, 50, 100], [10, 30, 50, 100]], "responsive": 'true', 'order': [[1, 'asc']], "pagingType": "full_numbers", + serverSide: true, + ajax: { + url: '/api/part/datatable', + type: 'POST', + datasource: 'data', + dataType: 'json', + contentType: 'application/json', + processData: false, // avoid being transformed into a query string, + "data": function (d) { + return JSON.stringify(d); + } + }, + columns: [ + { data: 'id' }, + { data: 'name' }, + { data: 'description' }, + ], + }); diff --git a/app/viewmodels/home/index_viewmodel.py b/app/viewmodels/home/index_viewmodel.py index 03a76f0..f61fc55 100644 --- a/app/viewmodels/home/index_viewmodel.py +++ b/app/viewmodels/home/index_viewmodel.py @@ -1,5 +1,8 @@ from starlette.requests import Request +from typing import List + +from app.schema.part import Part from app.services import part_service, project_service, storage_service from app.viewmodels.shared.viewmodel import ViewModelBase @@ -8,8 +11,17 @@ class IndexViewModel(ViewModelBase): def __init__(self, request: Request): super().__init__(request) - self.part_count: int = part_service.get_part_count() - self.location_count: int = storage_service.get_location_count() - self.project_count: int = project_service.get_project_count() - self.latest_parts = part_service.get_latest_parts(limit=7) - self.latest_projects = project_service.get_latest_projects(limit=7) + self.part_count: int = 0 + self.location_count: int = 0 + self.project_count: int = 0 + self.latest_parts: List[Part] = [] + self.latest_projects: List[ + Part + ] = [] # Todo: Change to correct Type once Project schema has been completed + + async def load(self): + self.part_count = await part_service.get_part_count() + self.location_count = await storage_service.get_location_count() + self.project_count = await project_service.get_project_count() + self.latest_parts = await part_service.get_latest_parts(limit=7) + self.latest_projects = await project_service.get_latest_projects(limit=7) diff --git a/app/viewmodels/parts/partslist_viewmodel.py b/app/viewmodels/parts/partslist_viewmodel.py index 10b549b..30b5d56 100644 --- a/app/viewmodels/parts/partslist_viewmodel.py +++ b/app/viewmodels/parts/partslist_viewmodel.py @@ -10,4 +10,7 @@ def __init__(self, request: Request): super().__init__(request) self.part_count: int = part_service.get_part_count() - self.parts = part_service.get_latest_parts(limit=30) + + async def load(self): + + self.part_count: int = await part_service.get_part_count() diff --git a/app/viewmodels/projects/projectslist_viewmodel.py b/app/viewmodels/projects/projectslist_viewmodel.py index 28fec3d..1af822d 100644 --- a/app/viewmodels/projects/projectslist_viewmodel.py +++ b/app/viewmodels/projects/projectslist_viewmodel.py @@ -3,10 +3,16 @@ from app.services import project_service from app.viewmodels.shared.viewmodel import ViewModelBase +from typing import List + class ProjectlistViewModel(ViewModelBase): def __init__(self, request: Request): super().__init__(request) + self.project_count: int = 0 + self.projects: List = [] + + def load(self): self.project_count: int = project_service.get_project_count() self.projects = project_service.get_latest_projects(limit=30) diff --git a/app/viewmodels/reports/overview_viewmodel.py b/app/viewmodels/reports/overview_viewmodel.py index 869994a..18c07ec 100644 --- a/app/viewmodels/reports/overview_viewmodel.py +++ b/app/viewmodels/reports/overview_viewmodel.py @@ -9,13 +9,25 @@ def __init__(self, request: Request): super().__init__(request) # Parts Stats - self.total_parts: int = part_service.get_part_count() - self.total_stock: int = part_service.get_total_stock() - self.stock_value: float = part_service.get_stock_value() + self.total_parts: int = 0 + self.total_stock: int = 0 + self.stock_value: float # Location Stats - self.locations_total: int = storage_service.get_location_count() - self.locations_used: int = storage_service.get_locations_used() + self.locations_total: int = 0 + self.locations_used: int = 0 + # Project Stats + self.project_count: int = 0 + + async def load(self): + # Parts Stats + self.total_parts = await part_service.get_part_count() + self.total_stock = await part_service.get_total_stock() + self.stock_value = await part_service.get_stock_value() + + # Location Stats + self.locations_total = await storage_service.get_location_count() + self.locations_used = await storage_service.get_locations_used() # Project Stats - self.project_count: int = project_service.get_project_count() + self.project_count = await project_service.get_project_count() diff --git a/app/viewmodels/storage/storagelist_viewmodel.py b/app/viewmodels/storage/storagelist_viewmodel.py index 06b42c3..044af9b 100644 --- a/app/viewmodels/storage/storagelist_viewmodel.py +++ b/app/viewmodels/storage/storagelist_viewmodel.py @@ -8,4 +8,7 @@ class StoragelistViewModel(ViewModelBase): def __init__(self, request: Request): super().__init__(request) - self.project_count: int = storage_service.get_location_count() + self.project_count: int = 0 + + async def load(self): + self.project_count = await storage_service.get_location_count() diff --git a/app/views/home.py b/app/views/home.py index f21ce7f..211c54d 100644 --- a/app/views/home.py +++ b/app/views/home.py @@ -10,8 +10,9 @@ @router.get("/") @template() -def index(request: Request): +async def index(request: Request): vm = IndexViewModel(request) + await vm.load() return vm.to_dict() diff --git a/app/views/parts.py b/app/views/parts.py index 6d2484c..f251cce 100644 --- a/app/views/parts.py +++ b/app/views/parts.py @@ -10,8 +10,9 @@ @router.get("/parts") @template() -def partslist(request: Request): +async def partslist(request: Request): vm = PartslistViewModel(request) + await vm.load() return vm.to_dict() diff --git a/app/views/projects.py b/app/views/projects.py index 943dfc2..b683724 100644 --- a/app/views/projects.py +++ b/app/views/projects.py @@ -10,8 +10,9 @@ @router.get("/projects") @template() -def projectlist(request: Request): +async def projectlist(request: Request): vm = ProjectlistViewModel(request) + vm.load() return vm.to_dict() diff --git a/app/views/reports.py b/app/views/reports.py index f42e517..92a355e 100644 --- a/app/views/reports.py +++ b/app/views/reports.py @@ -3,13 +3,13 @@ from starlette.requests import Request from app.viewmodels.reports.overview_viewmodel import OverviewViewModel -from app.viewmodels.shared.viewmodel import ViewModelBase router = fastapi.APIRouter() @router.get("/reports") @template() -def overview(request: Request): +async def overview(request: Request): vm = OverviewViewModel(request) + await vm.load() return vm.to_dict() diff --git a/app/views/storage.py b/app/views/storage.py index b40ae32..644dc76 100644 --- a/app/views/storage.py +++ b/app/views/storage.py @@ -10,8 +10,9 @@ @router.get("/storage") @template() -def storagelist(request: Request): +async def storagelist(request: Request): vm = StoragelistViewModel(request) + vm.load() return vm.to_dict() diff --git a/makefile b/makefile index fb41fcb..f2d2197 100644 --- a/makefile +++ b/makefile @@ -12,3 +12,6 @@ docs-serve: docs-build docs-clean: rm -rf site/ + +test: + pytest --no-cov diff --git a/poetry.lock b/poetry.lock index 7fe05c7..2c8551b 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,10 +1,10 @@ [[package]] name = "aiofiles" -version = "0.6.0" +version = "0.7.0" description = "File support for asyncio." category = "main" optional = false -python-versions = "*" +python-versions = ">=3.6,<4.0" [[package]] name = "aiosqlite" @@ -232,7 +232,7 @@ idna = ">=2.0.0" [[package]] name = "fastapi" -version = "0.63.0" +version = "0.64.0" description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production" category = "main" optional = false @@ -246,7 +246,7 @@ starlette = "0.13.6" all = ["requests (>=2.24.0,<3.0.0)", "aiofiles (>=0.5.0,<0.6.0)", "jinja2 (>=2.11.2,<3.0.0)", "python-multipart (>=0.0.5,<0.0.6)", "itsdangerous (>=1.1.0,<2.0.0)", "pyyaml (>=5.3.1,<6.0.0)", "graphene (>=2.1.8,<3.0.0)", "ujson (>=3.0.0,<4.0.0)", "orjson (>=3.2.1,<4.0.0)", "email_validator (>=1.1.1,<2.0.0)", "uvicorn[standard] (>=0.12.0,<0.14.0)", "async_exit_stack (>=1.0.1,<2.0.0)", "async_generator (>=1.10,<2.0.0)"] dev = ["python-jose[cryptography] (>=3.1.0,<4.0.0)", "passlib[bcrypt] (>=1.7.2,<2.0.0)", "autoflake (>=1.3.1,<2.0.0)", "flake8 (>=3.8.3,<4.0.0)", "uvicorn[standard] (>=0.12.0,<0.14.0)", "graphene (>=2.1.8,<3.0.0)"] doc = ["mkdocs (>=1.1.2,<2.0.0)", "mkdocs-material (>=6.1.4,<7.0.0)", "markdown-include (>=0.5.1,<0.6.0)", "mkdocs-markdownextradata-plugin (>=0.1.7,<0.2.0)", "typer-cli (>=0.0.9,<0.0.10)", "pyyaml (>=5.3.1,<6.0.0)"] -test = ["pytest (==5.4.3)", "pytest-cov (==2.10.0)", "pytest-asyncio (>=0.14.0,<0.15.0)", "mypy (==0.790)", "flake8 (>=3.8.3,<4.0.0)", "black (==20.8b1)", "isort (>=5.0.6,<6.0.0)", "requests (>=2.24.0,<3.0.0)", "httpx (>=0.14.0,<0.15.0)", "email_validator (>=1.1.1,<2.0.0)", "sqlalchemy (>=1.3.18,<2.0.0)", "peewee (>=3.13.3,<4.0.0)", "databases[sqlite] (>=0.3.2,<0.4.0)", "orjson (>=3.2.1,<4.0.0)", "async_exit_stack (>=1.0.1,<2.0.0)", "async_generator (>=1.10,<2.0.0)", "python-multipart (>=0.0.5,<0.0.6)", "aiofiles (>=0.5.0,<0.6.0)", "flask (>=1.1.2,<2.0.0)"] +test = ["pytest (==5.4.3)", "pytest-cov (==2.10.0)", "pytest-asyncio (>=0.14.0,<0.15.0)", "mypy (==0.812)", "flake8 (>=3.8.3,<4.0.0)", "black (==20.8b1)", "isort (>=5.0.6,<6.0.0)", "requests (>=2.24.0,<3.0.0)", "httpx (>=0.14.0,<0.15.0)", "email_validator (>=1.1.1,<2.0.0)", "sqlalchemy (>=1.3.18,<1.4.0)", "peewee (>=3.13.3,<4.0.0)", "databases[sqlite] (>=0.3.2,<0.4.0)", "orjson (>=3.2.1,<4.0.0)", "async_exit_stack (>=1.0.1,<2.0.0)", "async_generator (>=1.10,<2.0.0)", "python-multipart (>=0.0.5,<0.0.6)", "aiofiles (>=0.5.0,<0.6.0)", "flask (>=1.1.2,<2.0.0)"] [[package]] name = "fastapi-chameleon" @@ -676,16 +676,17 @@ python-versions = ">=3.5" [[package]] name = "pyjwt" -version = "1.7.1" +version = "2.1.0" description = "JSON Web Token implementation in Python" category = "main" optional = false -python-versions = "*" +python-versions = ">=3.6" [package.extras] -crypto = ["cryptography (>=1.4)"] -flake8 = ["flake8", "flake8-import-order", "pep8-naming"] -test = ["pytest (>=4.0.1,<5.0.0)", "pytest-cov (>=2.6.0,<3.0.0)", "pytest-runner (>=4.2,<5.0.0)"] +crypto = ["cryptography (>=3.3.1,<4.0.0)"] +dev = ["sphinx", "sphinx-rtd-theme", "zope.interface", "cryptography (>=3.3.1,<4.0.0)", "pytest (>=6.0.0,<7.0.0)", "coverage[toml] (==5.0.4)", "mypy", "pre-commit"] +docs = ["sphinx", "sphinx-rtd-theme", "zope.interface"] +tests = ["pytest (>=6.0.0,<7.0.0)", "coverage[toml] (==5.0.4)"] [[package]] name = "pymdown-extensions" @@ -729,17 +730,17 @@ testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xm [[package]] name = "pytest-asyncio" -version = "0.14.0" +version = "0.15.1" description = "Pytest support for asyncio." category = "dev" optional = false -python-versions = ">= 3.5" +python-versions = ">= 3.6" [package.dependencies] pytest = ">=5.4.0" [package.extras] -testing = ["async-generator (>=1.3)", "coverage", "hypothesis (>=5.7.1)"] +testing = ["coverage", "hypothesis (>=5.7.1)"] [[package]] name = "pytest-cov" @@ -810,11 +811,11 @@ six = ">=1.4.0" [[package]] name = "python-slugify" -version = "4.0.1" +version = "5.0.2" description = "A Python Slugify application that handles Unicode" category = "main" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +python-versions = ">=3.6" [package.dependencies] text-unidecode = ">=1.3" @@ -1065,12 +1066,12 @@ dev = ["pytest (>=4.6.2)", "black (>=19.3b0)"] [metadata] lock-version = "1.1" python-versions = "^3.9" -content-hash = "f6423b9aabc6d8a95a0b44bdb8f1a0abfd8412951649efc341feef6dbe24d266" +content-hash = "c391e18cb7afe78fb640384b229f3bf89bc42a5e5d3b24a2b00c04a138d78e2a" [metadata.files] aiofiles = [ - {file = "aiofiles-0.6.0-py3-none-any.whl", hash = "sha256:bd3019af67f83b739f8e4053c6c0512a7f545b9a8d91aaeab55e6e0f9d123c27"}, - {file = "aiofiles-0.6.0.tar.gz", hash = "sha256:e0281b157d3d5d59d803e3f4557dcc9a3dff28a4dd4829a9ff478adae50ca092"}, + {file = "aiofiles-0.7.0-py3-none-any.whl", hash = "sha256:c67a6823b5f23fcab0a2595a289cec7d8c863ffcb4322fb8cd6b90400aedfdbc"}, + {file = "aiofiles-0.7.0.tar.gz", hash = "sha256:a1c4fc9b2ff81568c83e21392a82f344ea9d23da906e4f6a52662764545e19d4"}, ] aiosqlite = [ {file = "aiosqlite-0.17.0-py3-none-any.whl", hash = "sha256:6c49dc6d3405929b1d08eeccc72306d3677503cc5e5e43771efc1e00232e8231"}, @@ -1239,8 +1240,8 @@ email-validator = [ {file = "email_validator-1.1.2-py2.py3-none-any.whl", hash = "sha256:094b1d1c60d790649989d38d34f69e1ef07792366277a2cf88684d03495d018f"}, ] fastapi = [ - {file = "fastapi-0.63.0-py3-none-any.whl", hash = "sha256:98d8ea9591d8512fdadf255d2a8fa56515cdd8624dca4af369da73727409508e"}, - {file = "fastapi-0.63.0.tar.gz", hash = "sha256:63c4592f5ef3edf30afa9a44fa7c6b7ccb20e0d3f68cd9eba07b44d552058dcb"}, + {file = "fastapi-0.64.0-py3-none-any.whl", hash = "sha256:62a438d0ff466640939414436339ce4e303964f3f823b7288e300baa869162e3"}, + {file = "fastapi-0.64.0.tar.gz", hash = "sha256:9bbd7b7b9291bbc3bbd72cbc82f5d456369802dab0d142a85350b06c5c7e6379"}, ] fastapi-chameleon = [] future = [ @@ -1505,8 +1506,8 @@ pygments = [ {file = "Pygments-2.9.0.tar.gz", hash = "sha256:a18f47b506a429f6f4b9df81bb02beab9ca21d0a5fee38ed15aef65f0545519f"}, ] pyjwt = [ - {file = "PyJWT-1.7.1-py2.py3-none-any.whl", hash = "sha256:5c6eca3c2940464d106b99ba83b00c6add741c9becaec087fb7ccdefea71350e"}, - {file = "PyJWT-1.7.1.tar.gz", hash = "sha256:8d59a976fb773f3e6a39c85636357c4f0e242707394cadadd9814f5cbaa20e96"}, + {file = "PyJWT-2.1.0-py3-none-any.whl", hash = "sha256:934d73fbba91b0483d3857d1aff50e96b2a892384ee2c17417ed3203f173fca1"}, + {file = "PyJWT-2.1.0.tar.gz", hash = "sha256:fba44e7898bbca160a2b2b501f492824fc8382485d3a6f11ba5d0c1937ce6130"}, ] pymdown-extensions = [ {file = "pymdown-extensions-8.2.tar.gz", hash = "sha256:b6daa94aad9e1310f9c64c8b1f01e4ce82937ab7eb53bfc92876a97aca02a6f4"}, @@ -1521,8 +1522,8 @@ pytest = [ {file = "pytest-6.2.4.tar.gz", hash = "sha256:50bcad0a0b9c5a72c8e4e7c9855a3ad496ca6a881a3641b4260605450772c54b"}, ] pytest-asyncio = [ - {file = "pytest-asyncio-0.14.0.tar.gz", hash = "sha256:9882c0c6b24429449f5f969a5158b528f39bde47dc32e85b9f0403965017e700"}, - {file = "pytest_asyncio-0.14.0-py3-none-any.whl", hash = "sha256:2eae1e34f6c68fc0a9dc12d4bea190483843ff4708d24277c41568d6b6044f1d"}, + {file = "pytest-asyncio-0.15.1.tar.gz", hash = "sha256:2564ceb9612bbd560d19ca4b41347b54e7835c2f792c504f698e05395ed63f6f"}, + {file = "pytest_asyncio-0.15.1-py3-none-any.whl", hash = "sha256:3042bcdf1c5d978f6b74d96a151c4cfb9dcece65006198389ccd7e6c60eb1eea"}, ] pytest-cov = [ {file = "pytest-cov-2.12.0.tar.gz", hash = "sha256:8535764137fecce504a49c2b742288e3d34bc09eed298ad65963616cc98fd45e"}, @@ -1550,7 +1551,8 @@ python-multipart = [ {file = "python-multipart-0.0.5.tar.gz", hash = "sha256:f7bb5f611fc600d15fa47b3974c8aa16e93724513b49b5f95c81e6624c83fa43"}, ] python-slugify = [ - {file = "python-slugify-4.0.1.tar.gz", hash = "sha256:69a517766e00c1268e5bbfc0d010a0a8508de0b18d30ad5a1ff357f8ae724270"}, + {file = "python-slugify-5.0.2.tar.gz", hash = "sha256:f13383a0b9fcbe649a1892b9c8eb4f8eab1d6d84b84bb7a624317afa98159cab"}, + {file = "python_slugify-5.0.2-py2.py3-none-any.whl", hash = "sha256:6d8c5df75cd4a7c3a2d21e257633de53f52ab0265cd2d1dc62a730e8194a7380"}, ] python-utils = [ {file = "python-utils-2.5.6.tar.gz", hash = "sha256:352d5b1febeebf9b3cdb9f3c87a3b26ef22d3c9e274a8ec1e7048ecd2fac4349"}, diff --git a/pyproject.toml b/pyproject.toml index e3cf096..a9fca3a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,12 +7,12 @@ license = "MIT" [tool.poetry.dependencies] python = "^3.9" -fastapi = "^0.63.0" +fastapi = "^0.64.0" pydantic = { version = "^1.6", extras = ["email"] } -pyjwt = "^1.7" +pyjwt = "^2.1" uvicorn = "^0.13.4" SQLAlchemy = "^1.4.13" -aiofiles = "^0.6.0" +aiofiles = "^0.7.0" python-multipart = "^0.0.5" progressbar2 = "^3.53.1" passlib = { version = "^1.7", extras = ["bcrypt"] } @@ -21,7 +21,7 @@ aiosqlite = "^0.17.0" fastapi-chameleon = {git = "https://github.com/mikeckennedy/fastapi-chameleon", rev = "main"} starlette = "0.13.6" alembic = "^1.4" -python-slugify = "^4.0" +python-slugify = "^5.0" Unidecode = "^1.1" loguru = "^0.5.1" python-dotenv = "^0.17.1" @@ -32,7 +32,7 @@ httptools = "^0.2.0" [tool.poetry.dev-dependencies] pytest = "^6.2.3" pytest-cov = "^2.10" -pytest-asyncio = "^0.14.0" +pytest-asyncio = "^0.15.1" pytest-env = "^0.6.2" black = "^21.4b2" docker = "^5.0.0" diff --git a/setup.cfg b/setup.cfg index 0423559..ccd7999 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,5 +1,10 @@ [flake8] ignore = D401,D202,E226,E302,E41 max-line-length=120 -exclude = migrations/* +exclude = + migrations/* + # TODO: remove these serivces once implemented + app/services/part_service.py + app/services/project_service.py + # max-complexity = 10 diff --git a/tests/conftest.py b/tests/conftest.py deleted file mode 100644 index 59293dd..0000000 --- a/tests/conftest.py +++ /dev/null @@ -1,52 +0,0 @@ -"""Defines fixtures available to all tests.""" - -import logging - -import pytest -from webtest import TestApp - -from maker_hub.app import create_app -from maker_hub.database import db as _db - -from .factories import UserFactory - - -@pytest.fixture -def app(): - """Create application for the tests.""" - _app = create_app("tests.settings") - _app.logger.setLevel(logging.CRITICAL) - ctx = _app.test_request_context() - ctx.push() - - yield _app - - ctx.pop() - - -@pytest.fixture -def testapp(app): - """Create Webtest app.""" - return TestApp(app) - - -@pytest.fixture -def db(app): - """Create database for the tests.""" - _db.app = app - with app.app_context(): - _db.create_all() - - yield _db - - # Explicitly close DB connection - _db.session.close() - _db.drop_all() - - -@pytest.fixture -def user(db): - """Create user for the tests.""" - user = UserFactory(password="myprecious") - db.session.commit() - return user diff --git a/tests/factories.py b/tests/factories.py deleted file mode 100644 index 1a62799..0000000 --- a/tests/factories.py +++ /dev/null @@ -1,30 +0,0 @@ -"""Factories to help in tests.""" -from factory import PostGenerationMethodCall, Sequence -from factory.alchemy import SQLAlchemyModelFactory - -from maker_hub.database import db -from maker_hub.user.models import User - - -class BaseFactory(SQLAlchemyModelFactory): - """Base factory.""" - - class Meta: - """Factory configuration.""" - - abstract = True - sqlalchemy_session = db.session - - -class UserFactory(BaseFactory): - """User factory.""" - - username = Sequence(lambda n: f"user{n}") - email = Sequence(lambda n: f"user{n}@example.com") - password = PostGenerationMethodCall("set_password", "example") - active = True - - class Meta: - """Factory configuration.""" - - model = User diff --git a/tests/settings.py b/tests/settings.py deleted file mode 100644 index e2f1785..0000000 --- a/tests/settings.py +++ /dev/null @@ -1,12 +0,0 @@ -"""Settings module for test app.""" -ENV = "development" -TESTING = True -SQLALCHEMY_DATABASE_URI = "sqlite://" -SECRET_KEY = "not-so-secret-in-tests" -BCRYPT_LOG_ROUNDS = ( - 4 # For faster tests; needs at least 4 to avoid "ValueError: Invalid rounds" -) -DEBUG_TB_ENABLED = False -CACHE_TYPE = "simple" # Can be "memcached", "redis", etc. -SQLALCHEMY_TRACK_MODIFICATIONS = False -WTF_CSRF_ENABLED = False # Allows form testing diff --git a/tests/test_forms.py b/tests/test_forms.py deleted file mode 100644 index dfaf5c1..0000000 --- a/tests/test_forms.py +++ /dev/null @@ -1,75 +0,0 @@ -"""Test forms.""" -from maker_hub.public.forms import LoginForm -from maker_hub.user.forms import RegisterForm - - -class TestRegisterForm: - """Register form.""" - - def test_validate_user_already_registered(self, user): - """Enter username that is already registered.""" - form = RegisterForm( - username=user.username, - email="foo@bar.com", - password="example", - confirm="example", - ) - - assert form.validate() is False - assert "Username already registered" in form.username.errors - - def test_validate_email_already_registered(self, user): - """Enter email that is already registered.""" - form = RegisterForm( - username="unique", email=user.email, password="example", confirm="example" - ) - - assert form.validate() is False - assert "Email already registered" in form.email.errors - - def test_validate_success(self, db): - """Register with success.""" - form = RegisterForm( - username="newusername", - email="new@test.test", - password="example", - confirm="example", - ) - assert form.validate() is True - - -class TestLoginForm: - """Login form.""" - - def test_validate_success(self, user): - """Login successful.""" - user.set_password("example") - user.save() - form = LoginForm(username=user.username, password="example") - assert form.validate() is True - assert form.user == user - - def test_validate_unknown_username(self, db): - """Unknown username.""" - form = LoginForm(username="unknown", password="example") - assert form.validate() is False - assert "Unknown username" in form.username.errors - assert form.user is None - - def test_validate_invalid_password(self, user): - """Invalid password.""" - user.set_password("example") - user.save() - form = LoginForm(username=user.username, password="wrongpassword") - assert form.validate() is False - assert "Invalid password" in form.password.errors - - def test_validate_inactive_user(self, user): - """Inactive user.""" - user.active = False - user.set_password("example") - user.save() - # Correct username and password, but user is not activated - form = LoginForm(username=user.username, password="example") - assert form.validate() is False - assert "User not activated" in form.username.errors diff --git a/tests/test_functional.py b/tests/test_functional.py deleted file mode 100644 index 186e9f5..0000000 --- a/tests/test_functional.py +++ /dev/null @@ -1,119 +0,0 @@ -"""Functional tests using WebTest. - -See: http://webtest.readthedocs.org/ -""" -from flask import url_for - -from maker_hub.user.models import User - -from .factories import UserFactory - - -class TestLoggingIn: - """Login.""" - - def test_can_log_in_returns_200(self, user, testapp): - """Login successful.""" - # Goes to homepage - res = testapp.get("/") - # Fills out login form in navbar - form = res.forms["loginForm"] - form["username"] = user.username - form["password"] = "myprecious" - # Submits - res = form.submit().follow() - assert res.status_code == 200 - - def test_sees_alert_on_log_out(self, user, testapp): - """Show alert on logout.""" - res = testapp.get("/") - # Fills out login form in navbar - form = res.forms["loginForm"] - form["username"] = user.username - form["password"] = "myprecious" - # Submits - res = form.submit().follow() - res = testapp.get(url_for("public.logout")).follow() - # sees alert - assert "You are logged out." in res - - def test_sees_error_message_if_password_is_incorrect(self, user, testapp): - """Show error if password is incorrect.""" - # Goes to homepage - res = testapp.get("/") - # Fills out login form, password incorrect - form = res.forms["loginForm"] - form["username"] = user.username - form["password"] = "wrong" - # Submits - res = form.submit() - # sees error - assert "Invalid password" in res - - def test_sees_error_message_if_username_doesnt_exist(self, user, testapp): - """Show error if username doesn't exist.""" - # Goes to homepage - res = testapp.get("/") - # Fills out login form, password incorrect - form = res.forms["loginForm"] - form["username"] = "unknown" - form["password"] = "myprecious" - # Submits - res = form.submit() - # sees error - assert "Unknown user" in res - - -class TestRegistering: - """Register a user.""" - - def test_can_register(self, user, testapp): - """Register a new user.""" - old_count = len(User.query.all()) - # Goes to homepage - res = testapp.get("/") - # Clicks Create Account button - res = res.click("Create account") - # Fills out the form - form = res.forms["registerForm"] - form["username"] = "foobar" - form["email"] = "foo@bar.com" - form["password"] = "secret" - form["confirm"] = "secret" - # Submits - res = form.submit().follow() - assert res.status_code == 200 - # A new user was created - assert len(User.query.all()) == old_count + 1 - - def test_sees_error_message_if_passwords_dont_match(self, user, testapp): - """Show error if passwords don't match.""" - # Goes to registration page - res = testapp.get(url_for("public.register")) - # Fills out form, but passwords don't match - form = res.forms["registerForm"] - form["username"] = "foobar" - form["email"] = "foo@bar.com" - form["password"] = "secret" - form["confirm"] = "secrets" - # Submits - res = form.submit() - # sees error message - assert "Passwords must match" in res - - def test_sees_error_message_if_user_already_registered(self, user, testapp): - """Show error if user already registered.""" - user = UserFactory(active=True) # A registered user - user.save() - # Goes to registration page - res = testapp.get(url_for("public.register")) - # Fills out form, but username is already registered - form = res.forms["registerForm"] - form["username"] = user.username - form["email"] = "foo@bar.com" - form["password"] = "secret" - form["confirm"] = "secret" - # Submits - res = form.submit() - # sees error - assert "Username already registered" in res diff --git a/tests/test_models.py b/tests/test_models.py deleted file mode 100644 index 5b7cf45..0000000 --- a/tests/test_models.py +++ /dev/null @@ -1,65 +0,0 @@ -"""Model unit tests.""" -import datetime as dt - -import pytest - -from maker_hub.user.models import Role, User - -from .factories import UserFactory - - -@pytest.mark.usefixtures("db") -class TestUser: - """User tests.""" - - def test_get_by_id(self): - """Get user by ID.""" - user = User("foo", "foo@bar.com") - user.save() - - retrieved = User.get_by_id(user.id) - assert retrieved == user - - def test_created_at_defaults_to_datetime(self): - """Test creation date.""" - user = User(username="foo", email="foo@bar.com") - user.save() - assert bool(user.created_at) - assert isinstance(user.created_at, dt.datetime) - - def test_password_is_nullable(self): - """Test null password.""" - user = User(username="foo", email="foo@bar.com") - user.save() - assert user.password is None - - def test_factory(self, db): - """Test user factory.""" - user = UserFactory(password="myprecious") - db.session.commit() - assert bool(user.username) - assert bool(user.email) - assert bool(user.created_at) - assert user.is_admin is False - assert user.active is True - assert user.check_password("myprecious") - - def test_check_password(self): - """Check password.""" - user = User.create(username="foo", email="foo@bar.com", password="foobarbaz123") - assert user.check_password("foobarbaz123") is True - assert user.check_password("barfoobaz") is False - - def test_full_name(self): - """User full name.""" - user = UserFactory(first_name="Foo", last_name="Bar") - assert user.full_name == "Foo Bar" - - def test_roles(self): - """Add a role to a user.""" - role = Role(name="admin") - role.save() - user = UserFactory() - user.roles.append(role) - user.save() - assert role in user.roles diff --git a/tests/test_services.py b/tests/test_services.py new file mode 100644 index 0000000..3af1c1e --- /dev/null +++ b/tests/test_services.py @@ -0,0 +1,70 @@ +import pytest +from app.services import part_service +from app.services import project_service +from app.services import storage_service + + +@pytest.mark.asyncio +class TestPartservice: + """Test part service""" + + async def test_get_part_count(self): + assert await part_service.get_part_count() == 283 + + async def test_get_total_stock(self): + assert await part_service.get_total_stock() == 1000 + + async def test_get_stock_value(self): + assert await part_service.get_stock_value() == 1_500 + + async def test_get_latest_parts_returns_a_list(self): + assert type(await part_service.get_latest_parts()) == list + + async def test_get_latest_parts_limits(self): + result = len(await part_service.get_latest_parts()) + assert result == 5 + + result = len(await part_service.get_latest_parts(limit=10)) + assert result == 10 + + result = len(await part_service.get_latest_parts(start=5, limit=10)) + assert result == 10 + + +@pytest.mark.asyncio +class TestProjectservice: + """Test project service""" + + async def test_get_project_count(self): + assert await project_service.get_project_count() == 34 + + async def test_get_latest_projects_returns_a_list(self): + assert type(await project_service.get_latest_projects()) == list + + async def test_get_latest_parts_limits(self): + result = len(await project_service.get_latest_projects()) + assert result == 5 + + result = len(await project_service.get_latest_projects(limit=10)) + assert result == 10 + + result = len(await project_service.get_latest_projects(start=5, limit=10)) + assert result == 5 + + result = len(await project_service.get_latest_projects(start=-1)) + assert result == 5 + + result = len(await project_service.get_latest_projects(limit=-1)) + assert result == 0 + + result = len(await project_service.get_latest_projects(limit=0)) + assert result == 0 + + +@pytest.mark.asyncio +class TestStorageservice: + async def test_get_location_count(self): + assert await storage_service.get_location_count() == 234 + + async def test_get_locations_used(self): + assert await storage_service.get_locations_used() == 230