Michael-Antony's picture
feat: implement tasks API with GET today and PATCH status endpoints
32eb084
"""
Main FastAPI application for Tracker Microservice.
"""
import os
from fastapi import FastAPI, Request, status
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import JSONResponse
from fastapi.exceptions import RequestValidationError
from jose import JWTError
from app.core.config import settings
from app.core.logging import setup_logging, get_logger
from app.nosql import connect_to_mongo, close_mongo_connection
from app.postgres import connect_to_postgres, close_postgres_connection
from app.tracker.attendance.router import router as attendance_router
from app.tracker.tasks.router import router as tasks_router
# Initialize logging first
log_level = getattr(settings, 'LOG_LEVEL', 'INFO').strip().upper()
if log_level not in ['DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL']:
log_level = 'INFO'
setup_logging(
level=log_level,
format_type="colored",
app_name="tracker-microservice",
include_correlation=True
)
logger = get_logger(__name__)
# Create FastAPI app
app = FastAPI(
title=settings.APP_NAME,
description="Employee Tracker - Attendance and Location Tracking",
version=settings.APP_VERSION,
docs_url="/docs",
redoc_url="/redoc",
root_path=os.getenv("ROOT_PATH", ""),
)
# CORS middleware
app.add_middleware(
CORSMiddleware,
allow_origins=settings.CORS_ORIGINS,
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
expose_headers=["*"],
)
# Startup and shutdown events
@app.on_event("startup")
async def startup_event():
"""Initialize connections on startup"""
logger.info("Starting Tracker Microservice")
await connect_to_mongo()
await connect_to_postgres()
# Create schema and tables
try:
from app.tracker.attendance.models import ScmAttendance
conn = None
try:
from app.postgres import get_postgres_connection, release_postgres_connection
conn = await get_postgres_connection()
# Create trans schema
await conn.execute("CREATE SCHEMA IF NOT EXISTS trans")
logger.info("✅ TRANS schema exists")
# Create table
create_table_sql = """
CREATE TABLE IF NOT EXISTS trans.scm_attendance (
id UUID PRIMARY KEY,
merchant_id UUID NOT NULL,
employee_id UUID NOT NULL,
work_date DATE NOT NULL,
check_in_time BIGINT,
check_in_lat DOUBLE PRECISION,
check_in_lon DOUBLE PRECISION,
check_in_geofence_id UUID,
check_out_time BIGINT,
check_out_lat DOUBLE PRECISION,
check_out_lon DOUBLE PRECISION,
total_minutes INTEGER,
created_at TIMESTAMP DEFAULT now(),
updated_at TIMESTAMP DEFAULT now(),
UNIQUE (employee_id, work_date)
)
"""
await conn.execute(create_table_sql)
logger.info("✅ scm_attendance table created/verified")
# Create indexes
index_sql = """
CREATE INDEX IF NOT EXISTS idx_scm_attendance_work_date
ON trans.scm_attendance (employee_id, work_date)
"""
await conn.execute(index_sql)
index_merchant_sql = """
CREATE INDEX IF NOT EXISTS idx_scm_attendance_merchant
ON trans.scm_attendance (merchant_id, work_date)
"""
await conn.execute(index_merchant_sql)
logger.info("✅ Indexes created/verified")
finally:
if conn:
await release_postgres_connection(conn)
except Exception as e:
logger.error("Failed to create database schema", exc_info=e)
raise
logger.info("Tracker Microservice started successfully")
@app.on_event("shutdown")
async def shutdown_event():
"""Close connections on shutdown"""
logger.info("Shutting down Tracker Microservice")
await close_mongo_connection()
await close_postgres_connection()
logger.info("Tracker Microservice shut down successfully")
# Health check endpoint
@app.get("/health", tags=["health"])
async def health_check():
"""Health check endpoint"""
return {
"status": "healthy",
"service": "tracker-microservice",
"version": settings.APP_VERSION
}
# Include routers
app.include_router(attendance_router, prefix="/tracker")
app.include_router(tasks_router, prefix="/tracker")
# Global exception handlers
@app.exception_handler(RequestValidationError)
async def validation_exception_handler(request: Request, exc: RequestValidationError):
errors = [
{
"field": " -> ".join(str(loc) for loc in error["loc"]),
"message": error["msg"],
"type": error["type"]
}
for error in exc.errors()
]
logger.warning(
"Validation error",
extra={
"path": request.url.path,
"method": request.method,
"error_count": len(errors),
"errors": errors
}
)
return JSONResponse(
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
content={
"success": False,
"error": "Validation Error",
"errors": errors
}
)
@app.exception_handler(JWTError)
async def jwt_exception_handler(request: Request, exc: JWTError):
logger.warning(
"JWT authentication failed",
extra={
"path": request.url.path,
"error": str(exc),
"client_ip": request.client.host if request.client else None
}
)
return JSONResponse(
status_code=status.HTTP_401_UNAUTHORIZED,
content={
"success": False,
"error": "Unauthorized",
"detail": "Invalid or expired token"
}
)
@app.exception_handler(Exception)
async def general_exception_handler(request: Request, exc: Exception):
logger.error(
"Unhandled exception",
extra={
"method": request.method,
"path": request.url.path,
"error": str(exc),
"error_type": type(exc).__name__,
"client_ip": request.client.host if request.client else None
},
exc_info=True
)
return JSONResponse(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
content={
"success": False,
"error": "Internal Server Error",
"detail": "An unexpected error occurred"
}
)
if __name__ == "__main__":
import uvicorn
uvicorn.run(
"app.main:app",
host="0.0.0.0",
port=int(os.getenv("PORT", "8003")),
reload=True,
log_level=log_level.lower()
)