from contextlib import asynccontextmanager from fastapi import FastAPI from .exceptions import ExceptionHandlerRoute from fastapi.middleware.cors import CORSMiddleware from sqlalchemy.orm import Session from fastapi.staticfiles import StaticFiles from .models import Users from .db.db import engine from .db.config import settings from .middlewares import app_middleware from .auth.views import get_password_hash from .routes import routers, docs @asynccontextmanager async def init_db(app: FastAPI): with Session(engine) as session: from sqlalchemy import select, insert user = session.execute(select(Users).where(Users.username == settings.FIRST_SUPERUSER)).first() if not user: session.execute(insert(Users).values( username = settings.FIRST_SUPERUSER, hashed_password = get_password_hash(settings.FIRST_SUPERUSER_PASSWORD), full_name = "admin", type = 'ADMIN', email = settings.FIRST_SUPERUSER_EMAIL )) session.commit() yield description = """ # Description This part of the documentation includes overview of the backend, backend internals and common attributes shared by all the apis. The backend is written using [FastAPI](https://fastapi.tiangolo.com/), [SQLAlchemy](https://www.sqlalchemy.org/) and [Postgresql](https://www.postgresql.org/). ## Documentation Redoc with openapi will serve as primary documentation, which will be available publicly in development and staging environment and not for production evnrionment for security reasons. ## Authorization Authorization is implemended using OAuth2 using password and username, logging with external providers like google, github etc. is not supported. Each type of user i.e. doctor, patient, staff is required to create their own username and password while registering, so that will be able to login to the system. The auth token expiry duration is 120 minutes currently ## Common columns All tables in the backend shares some common columns, these columns will be described here and not in the respective API as it has the same meaning everywhere. 1. __created_at, updated_at__ : represents the datetime when the row was added, updated respectively 2. __create_by_id, updated_by_id__ : foreign key to user's id, it's extracted from the logged in user who created or updated the row. 3. __is_archived__ : It represents if the row is deleted, allows us to implement soft delete. ## Audit Each table on the backend has it's respective audit table with suffix ___audit__. i.e __appointment__ table has __appointment_audit__. It stores all the changes done in each row including which operation it was, which columns were changed and what's it's old and new values. APIs will be created to access the audit for each table. It's implemented using sqlalchemy_declarative_extention library ## Internals ### Primary, Foreign Key All primary keys are [UUID](https://en.wikipedia.org/wiki/Universally_unique_identifier), which makes it possible to avoid alot of security risks and implement dynamic features. Due to this all the foreign keys are also UUIDs UUIDs can be generated in postgres or in python easy and fast ## Errors Errors are implemented using [RFC9457](https://www.rfc-editor.org/rfc/rfc9457.html) with some slight modification. Here is a sample error response. ```json { "type": "UNIQUE_VALIDATION", "status": 422, "detail": "Key (username)=(banana) already exists.", "title": "Unique violation" } ``` The type will always be a standard enum value. the status will always be the http status code. The type, status, title and detail will __always__ exist, For now in case of pydantic validation error, the raw pydantic message will be set in details In case of internal server error the type will be 'INTERNAL_SERVER_ERROR' and it's detail will contain the error message. In this way all the errors will be well documented and handled in a central module, It will also make writing automated tests easier. # Testing This section covers the steps for testing and guidelines for reporting and verifying bugs for backend api. In the future the manual for QA for frontend will also be included here. ## Automated testing Automated testing is done using fastapi's testclient, pytest and faker This test creates new database on startup, runs the tests and deletes everything, so the errors i can be reproduceable and deterministic. The test interacts to the backend only thru API ### Prerequisites 1. Python 2. Postgresql server with a clean database (You can avoid it by using test database on the server) ### Steps 1. Clone the [repository](https://git.aayutech.dev/fourleaf/backend) 2. Create .env file using .env.example as reference 3. Create virtual environment and install packages from requirements.txt 4. Run tests using pytest (lookup pytest's documentation for more information) 5. Create a test inside the tests folder ## Manual testing Manual testing is done using postman or similar api client. Url for testing in dev environment is `https://api-dev.aayutech.dev` ### Steps 1. Download postmand collection from the git server (optional) 2. Create and run requests from postman ## Reporting bug All the bug reporting will be done using kanban `https://project.aayutech.dev` ### Bug criteria 1. All __INTERNAL_SERVER_ERROR__ 2. All about:none errors 3. All errors that consists of raw sql messages 4. Errors that doesn't match the description of the api 5. Bug should be able to be reproduced ### Bug reporting steps 1. Create new task in the `Bug Report` column in kanboard 2. Write short description about the bug and how it can be reproduced 3. Optionally take screenshot of postman or terminal where the bug can be seen 4. Add related user as assigne ### Bug resolve steps 1. After a bug is reported a QA or Developer will approve the bug and put it in the `BUG` column. 2. After the bug is verified, the respective developer will fix the bug and put it in the `Read for QA` column. 3. A QA will reverify the bug and put it in the `DONE` column. """ app = FastAPI( version = "0.0.1", lifespan = init_db, title = "Fastapi template", openapi_tags = docs, description = description ) for router in routers: app.include_router(router) app.middleware('http')(app_middleware) app.add_middleware( CORSMiddleware ,allow_origins=["*"] ,allow_credentials=True # Set to True if you need to support cookies/authorization headers ,allow_methods=["*"] # Allows all methods (GET, POST, PUT, DELETE, OPTIONS, etc.) ,allow_headers=["*"] # Allows all headers )