|
1 | 1 | from fastapi import FastAPI, UploadFile, File, Depends, HTTPException, Form |
2 | 2 | from fastapi.middleware.cors import CORSMiddleware |
3 | | -from sqlalchemy.orm import Session |
| 3 | +from sqlalchemy import create_engine, Column, Integer, String, Date, Text |
| 4 | +from sqlalchemy.orm import sessionmaker, declarative_base, Session |
4 | 5 | from pydantic import BaseModel |
5 | 6 | from datetime import date |
6 | 7 | import os, shutil |
7 | 8 | from dotenv import load_dotenv |
8 | | -import requests |
9 | 9 |
|
10 | | -# ✅ Import from database (moved get_db here to avoid circular import) |
11 | | -from app.database import Base, engine, get_db |
12 | | - |
13 | | -# ✅ Import users router (no circular import now) |
14 | | -from app.routers import users |
15 | | - |
16 | | -# Optional OpenAI |
17 | | -try: |
18 | | - from openai import OpenAI |
19 | | -except Exception: |
20 | | - OpenAI = None |
21 | | - |
22 | | -# Load environment variables |
| 10 | +# ⬇️ your database bits |
23 | 11 | load_dotenv() |
| 12 | +DATABASE_URL = os.getenv("DATABASE_URL", "sqlite:///./app.db") |
| 13 | +engine = create_engine(DATABASE_URL, pool_pre_ping=True) |
| 14 | +SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) |
| 15 | +Base = declarative_base() |
| 16 | + |
| 17 | +# ⬇️ import models so SQLAlchemy knows about User |
| 18 | +from app import models |
| 19 | +# … define Task/AuditLog classes … |
| 20 | +Base.metadata.create_all(bind=engine) |
24 | 21 |
|
25 | | -# === FastAPI App === |
26 | 22 | app = FastAPI(title="FAT-EIBL (Edme) – API") |
27 | 23 |
|
28 | | -# === CORS === |
| 24 | +# CORS |
29 | 25 | allow = os.getenv("ALLOW_ORIGINS", "*").split(",") |
30 | 26 | app.add_middleware( |
31 | 27 | CORSMiddleware, |
|
35 | 31 | allow_headers=["*"], |
36 | 32 | ) |
37 | 33 |
|
38 | | -# === Models (local models only for tasks/audit) === |
39 | | -from sqlalchemy import Column, Integer, String, Date, Text |
40 | | - |
41 | | -class Task(Base): |
42 | | - __tablename__ = "tasks" |
43 | | - id = Column(Integer, primary_key=True, index=True) |
44 | | - title = Column(String(255), nullable=False) |
45 | | - department = Column(String(100), nullable=True) |
46 | | - assignee = Column(String(100), nullable=True) |
47 | | - status = Column(String(50), nullable=False, default="Pending") |
48 | | - due_date = Column(Date, nullable=True) |
49 | | - priority = Column(String(20), nullable=True) |
50 | | - remarks = Column(Text, nullable=True) |
51 | | - attachment = Column(String(255), nullable=True) |
52 | | - |
53 | | -class AuditLog(Base): |
54 | | - __tablename__ = "audit_logs" |
55 | | - id = Column(Integer, primary_key=True) |
56 | | - action = Column(String(50)) |
57 | | - detail = Column(Text) |
58 | | - |
59 | | -# ✅ Create all tables (includes Task, AuditLog, and user models) |
60 | | -Base.metadata.create_all(bind=engine) |
61 | | - |
62 | | -# === Pydantic Schemas === |
63 | | -class TaskCreate(BaseModel): |
64 | | - title: str |
65 | | - department: str | None = None |
66 | | - assignee: str | None = None |
67 | | - status: str = "Pending" |
68 | | - due_date: date | None = None |
69 | | - priority: str | None = None |
70 | | - remarks: str | None = None |
71 | | - |
72 | | -class TaskOut(BaseModel): |
73 | | - id: int |
74 | | - title: str |
75 | | - department: str | None |
76 | | - assignee: str | None |
77 | | - status: str |
78 | | - due_date: date | None |
79 | | - priority: str | None |
80 | | - remarks: str | None |
81 | | - attachment: str | None |
82 | | - |
83 | | - class Config: |
84 | | - from_attributes = True |
85 | | - |
86 | | -# === Routes === |
87 | | - |
88 | | -@app.get("/health") |
89 | | -def health(): |
90 | | - return {"status": "ok"} |
91 | | - |
92 | | -# --- TASK CRUD --- |
93 | | -@app.get("/tasks", response_model=list[TaskOut]) |
94 | | -def list_tasks(db: Session = Depends(get_db)): |
95 | | - return db.query(Task).order_by(Task.due_date.is_(None), Task.due_date).all() |
96 | | - |
97 | | -@app.post("/tasks", response_model=TaskOut) |
98 | | -def create_task(task: TaskCreate, db: Session = Depends(get_db)): |
99 | | - obj = Task(**task.dict()) |
100 | | - db.add(obj) |
101 | | - db.add(AuditLog(action="create", detail=f"Task: {obj.title}")) |
102 | | - db.commit() |
103 | | - db.refresh(obj) |
104 | | - return obj |
105 | | - |
106 | | -@app.put("/tasks/{task_id}", response_model=TaskOut) |
107 | | -def update_task(task_id: int, task: TaskCreate, db: Session = Depends(get_db)): |
108 | | - obj = db.get(Task, task_id) |
109 | | - if not obj: |
110 | | - raise HTTPException(status_code=404, detail="Task not found") |
111 | | - for k, v in task.dict().items(): |
112 | | - setattr(obj, k, v) |
113 | | - db.add(AuditLog(action="update", detail=f"Task ID {task_id}")) |
114 | | - db.commit() |
115 | | - db.refresh(obj) |
116 | | - return obj |
117 | | - |
118 | | -@app.delete("/tasks/{task_id}") |
119 | | -def delete_task(task_id: int, db: Session = Depends(get_db)): |
120 | | - obj = db.get(Task, task_id) |
121 | | - if not obj: |
122 | | - raise HTTPException(status_code=404, detail="Task not found") |
123 | | - db.delete(obj) |
124 | | - db.add(AuditLog(action="delete", detail=f"Task ID {task_id}")) |
125 | | - db.commit() |
126 | | - return {"ok": True} |
127 | | - |
128 | | -# --- UPLOAD --- |
129 | | -UPLOAD_DIR = os.path.join(os.getcwd(), "uploads") |
130 | | -os.makedirs(UPLOAD_DIR, exist_ok=True) |
131 | | - |
132 | | -@app.post("/upload/{task_id}") |
133 | | -async def upload_file(task_id: int, file: UploadFile = File(...), db: Session = Depends(get_db)): |
134 | | - obj = db.get(Task, task_id) |
135 | | - if not obj: |
136 | | - raise HTTPException(status_code=404, detail="Task not found") |
137 | | - safe_name = f"task_{task_id}_" + os.path.basename(file.filename) |
138 | | - dest = os.path.join(UPLOAD_DIR, safe_name) |
139 | | - with open(dest, "wb") as buffer: |
140 | | - shutil.copyfileobj(file.file, buffer) |
141 | | - obj.attachment = safe_name |
142 | | - db.add(AuditLog(action="upload", detail=f"Task ID {task_id} file {safe_name}")) |
143 | | - db.commit() |
144 | | - return {"task_id": task_id, "filename": safe_name} |
145 | | - |
146 | | -# --- SEED --- |
147 | | -@app.get("/seed") |
148 | | -def seed(db: Session = Depends(get_db)): |
149 | | - if db.query(Task).count() == 0: |
150 | | - for t in [ |
151 | | - Task(title="IRDAI Filing Q3", department="Compliance", assignee="Darpan", status="Pending", priority="High"), |
152 | | - Task(title="Statutory Audit FY 24-25", department="Accounts", assignee="Priya", status="In Progress", priority="High"), |
153 | | - Task(title="Investment Reconciliation", department="Ops", assignee="Rakesh", status="Pending", priority="Medium"), |
154 | | - ]: |
155 | | - db.add(t) |
156 | | - db.add(AuditLog(action="seed", detail="Sample tasks added")) |
157 | | - db.commit() |
158 | | - return {"seeded": True} |
159 | | - |
160 | | -# --- AI CHAT --- |
161 | | -@app.post("/ai/chat") |
162 | | -def ai_chat(prompt: str = Form(...)): |
163 | | - api_key = os.getenv("OPENAI_API_KEY", "") |
164 | | - if not api_key or OpenAI is None: |
165 | | - return {"reply": "AI is not configured. Please set OPENAI_API_KEY in backend/.env."} |
166 | | - client = OpenAI(api_key=api_key) |
167 | | - try: |
168 | | - resp = client.chat.completions.create( |
169 | | - model="gpt-4o-mini", |
170 | | - messages=[ |
171 | | - {"role": "system", "content": "You are Vani, an AI assistant for an audit tracker."}, |
172 | | - {"role": "user", "content": prompt} |
173 | | - ] |
174 | | - ) |
175 | | - return {"reply": resp.choices[0].message.content} |
176 | | - except Exception as e: |
177 | | - return {"reply": f"AI error: {e}"} |
178 | | - |
179 | | -# --- USER ROUTES --- |
| 34 | +# at the bottom: include users router |
| 35 | +from app.routers import users |
180 | 36 | app.include_router(users.router, prefix="/users", tags=["Users"]) |
181 | | - |
0 commit comments