Files
2026-05-03 09:14:27 +05:45

176 lines
6.8 KiB
Python

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):
#Create a admin user and password at the beggining
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
)