Skip to content

Commit d5f94d5

Browse files
author
Uttam Singh
committed
Initial commit - Full FAT-EIBL AI-enabled build
0 parents  commit d5f94d5

File tree

19 files changed

+578
-0
lines changed

19 files changed

+578
-0
lines changed

.github/workflows/deploy.yml

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
name: Deploy Frontend to GitHub Pages
2+
on:
3+
push:
4+
branches: [ main ]
5+
6+
jobs:
7+
build-and-deploy:
8+
runs-on: ubuntu-latest
9+
steps:
10+
- uses: actions/checkout@v3
11+
- name: Setup Node
12+
uses: actions/setup-node@v3
13+
with:
14+
node-version: '20'
15+
- name: Build
16+
run: |
17+
cd frontend
18+
npm ci || npm install
19+
npm run build
20+
- name: Deploy
21+
uses: peaceiris/actions-gh-pages@v3
22+
with:
23+
github_token: ${{ secrets.GITHUB_TOKEN }}
24+
publish_dir: frontend/dist

README.md

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
# FAT-EIBL – Full (Edme Insurance Brokers Limited)
2+
3+
Complete ready-to-deploy stack:
4+
- Frontend: React + Vite (theme color auto-extracted from logo)
5+
- Backend: FastAPI (Tasks, Uploads, AI chat), SQLAlchemy
6+
- Database: PostgreSQL (Docker)
7+
- AI: OpenAI GPT (set OPENAI_API_KEY in backend/.env)
8+
- Voice: Web Speech API (“Hey Vani”)
9+
- Docker Compose + GitHub Actions (Pages deploy)
10+
11+
## Quick Start (Local, Docker)
12+
```bash
13+
docker compose up --build
14+
# Seed demo data
15+
curl http://localhost:8000/seed
16+
# Open UI
17+
http://localhost:3000
18+
```
19+
Backend docs: http://localhost:8000/docs
20+
21+
## Configure
22+
- Frontend reads API at `VITE_API_URL` (set in docker-compose to http://localhost:8000)
23+
- Backend env in `backend/.env`:
24+
```
25+
DATABASE_URL=postgresql+psycopg2://fatuser:fatpass@db:5432/fatdb
26+
OPENAI_API_KEY= # paste your key
27+
28+
SECRET_KEY=dev-secret
29+
```
30+
31+
## Deploy Options
32+
- **Render**: create two services -> backend (Docker, root `backend/`), frontend (Static Site, root `frontend/`)
33+
- **Railway**: deploy repo; it will build Docker images
34+
- **GitHub Pages**: GitHub Action in `.github/workflows/deploy.yml` will publish `frontend/dist`
35+
36+
## Branding
37+
- Logo placed at `frontend/public/logo.png` and used as favicon
38+
- Theme color extracted from logo: `rgb(175, 157, 165)`
39+
40+
## Modules Included
41+
- Dashboard (KPIs)
42+
- Work Task Listing + per-row file Uploader
43+
- AI Chat (“Hey Vani”)
44+
- Simple Audit Trail

backend/.env

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# Backend Environment
2+
DATABASE_URL=postgresql+psycopg2://fatuser:fatpass@db:5432/fatdb
3+
SECRET_KEY=dev-secret
4+
5+
SMTP_HOST=
6+
SMTP_PORT=587
7+
SMTP_USER=
8+
SMTP_PASS=
9+
OPENAI_API_KEY=
10+
ALLOW_ORIGINS=*

backend/.env.example

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# Backend Environment
2+
DATABASE_URL=postgresql+psycopg2://fatuser:fatpass@db:5432/fatdb
3+
SECRET_KEY=dev-secret
4+
5+
SMTP_HOST=
6+
SMTP_PORT=587
7+
SMTP_USER=
8+
SMTP_PASS=
9+
OPENAI_API_KEY=
10+
ALLOW_ORIGINS=*

backend/Dockerfile

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
FROM python:3.11-slim
2+
ENV PYTHONDONTWRITEBYTECODE=1
3+
ENV PYTHONUNBUFFERED=1
4+
WORKDIR /app
5+
6+
RUN apt-get update && apt-get install -y build-essential && rm -rf /var/lib/apt/lists/*
7+
8+
COPY requirements.txt .
9+
RUN pip install --no-cache-dir -r requirements.txt
10+
11+
COPY . /app
12+
13+
EXPOSE 8000
14+
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]

backend/app/main.py

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
from fastapi import FastAPI, UploadFile, File, Depends, HTTPException, Form
2+
from fastapi.middleware.cors import CORSMiddleware
3+
from sqlalchemy import create_engine, Column, Integer, String, Date, Text, func
4+
from sqlalchemy.orm import sessionmaker, declarative_base, Session
5+
from pydantic import BaseModel
6+
from datetime import date
7+
import os, shutil
8+
from dotenv import load_dotenv
9+
import requests
10+
11+
# Optional OpenAI
12+
try:
13+
from openai import OpenAI
14+
except Exception:
15+
OpenAI = None
16+
17+
load_dotenv()
18+
19+
DATABASE_URL = os.getenv("DATABASE_URL", "sqlite:///./app.db")
20+
engine = create_engine(DATABASE_URL, pool_pre_ping=True)
21+
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
22+
Base = declarative_base()
23+
24+
class Task(Base):
25+
__tablename__ = "tasks"
26+
id = Column(Integer, primary_key=True, index=True)
27+
title = Column(String(255), nullable=False)
28+
department = Column(String(100), nullable=True)
29+
assignee = Column(String(100), nullable=True)
30+
status = Column(String(50), nullable=False, default="Pending")
31+
due_date = Column(Date, nullable=True)
32+
priority = Column(String(20), nullable=True)
33+
remarks = Column(Text, nullable=True)
34+
attachment = Column(String(255), nullable=True)
35+
36+
class AuditLog(Base):
37+
__tablename__ = "audit_logs"
38+
id = Column(Integer, primary_key=True)
39+
action = Column(String(50))
40+
detail = Column(Text)
41+
42+
Base.metadata.create_all(bind=engine)
43+
44+
def get_db():
45+
db = SessionLocal()
46+
try:
47+
yield db
48+
finally:
49+
db.close()
50+
51+
app = FastAPI(title="FAT-EIBL (Edme) – API")
52+
53+
allow = os.getenv("ALLOW_ORIGINS", "*").split(",")
54+
app.add_middleware(
55+
CORSMiddleware,
56+
allow_origins=allow,
57+
allow_credentials=True,
58+
allow_methods=["*"],
59+
allow_headers=["*"],
60+
)
61+
62+
# Models
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+
class Config:
83+
from_attributes = True
84+
85+
# Health
86+
@app.get("/health")
87+
def health(): return { "status": "ok" }
88+
89+
# Tasks CRUD
90+
@app.get("/tasks", response_model=list[TaskOut])
91+
def list_tasks(db: Session = Depends(get_db)):
92+
return db.query(Task).order_by(Task.due_date.is_(None), Task.due_date).all()
93+
94+
@app.post("/tasks", response_model=TaskOut)
95+
def create_task(task: TaskCreate, db: Session = Depends(get_db)):
96+
obj = Task(**task.dict())
97+
db.add(obj)
98+
db.add(AuditLog(action="create", detail=f"Task: {obj.title}"))
99+
db.commit()
100+
db.refresh(obj)
101+
return obj
102+
103+
@app.put("/tasks/{task_id}", response_model=TaskOut)
104+
def update_task(task_id: int, task: TaskCreate, db: Session = Depends(get_db)):
105+
obj = db.get(Task, task_id)
106+
if not obj: raise HTTPException(status_code=404, detail="Task not found")
107+
for k, v in task.dict().items(): setattr(obj, k, v)
108+
db.add(AuditLog(action="update", detail=f"Task ID {task_id}"))
109+
db.commit(); db.refresh(obj)
110+
return obj
111+
112+
@app.delete("/tasks/{task_id}")
113+
def delete_task(task_id: int, db: Session = Depends(get_db)):
114+
obj = db.get(Task, task_id)
115+
if not obj: raise HTTPException(status_code=404, detail="Task not found")
116+
db.delete(obj); db.add(AuditLog(action="delete", detail=f"Task ID {task_id}"))
117+
db.commit(); return { "ok": True }
118+
119+
# Uploads
120+
UPLOAD_DIR = os.path.join(os.getcwd(), "uploads"); os.makedirs(UPLOAD_DIR, exist_ok=True)
121+
122+
@app.post("/upload/{task_id}")
123+
async def upload_file(task_id: int, file: UploadFile = File(...), db: Session = Depends(get_db)):
124+
obj = db.get(Task, task_id)
125+
if not obj: raise HTTPException(status_code=404, detail="Task not found")
126+
safe_name = f"task_{task_id}_" + os.path.basename(file.filename)
127+
dest = os.path.join(UPLOAD_DIR, safe_name)
128+
with open(dest, "wb") as buffer: shutil.copyfileobj(file.file, buffer)
129+
obj.attachment = safe_name
130+
db.add(AuditLog(action="upload", detail=f"Task ID {task_id} file {safe_name}"))
131+
db.commit()
132+
return { "task_id": task_id, "filename": safe_name }
133+
134+
# Seed
135+
@app.get("/seed")
136+
def seed(db: Session = Depends(get_db)):
137+
if db.query(Task).count() == 0:
138+
for t in [
139+
Task(title="IRDAI Filing Q3", department="Compliance", assignee="Darpan", status="Pending", priority="High"),
140+
Task(title="Statutory Audit FY 24-25", department="Accounts", assignee="Priya", status="In Progress", priority="High"),
141+
Task(title="Investment Reconciliation", department="Ops", assignee="Rakesh", status="Pending", priority="Medium"),
142+
]:
143+
db.add(t)
144+
db.add(AuditLog(action="seed", detail="Sample tasks added"))
145+
db.commit()
146+
return { "seeded": True }
147+
148+
# AI Chat (proxy to OpenAI if key provided)
149+
@app.post("/ai/chat")
150+
def ai_chat(prompt: str = Form(...)):
151+
api_key = os.getenv("OPENAI_API_KEY", "")
152+
if not api_key or OpenAI is None:
153+
return { "reply": "AI is not configured. Please set OPENAI_API_KEY in backend/.env." }
154+
client = OpenAI(api_key=api_key)
155+
try:
156+
resp = client.chat.completions.create(
157+
model="gpt-4o-mini",
158+
messages=[{"role":"system","content":"You are Vani, an AI assistant for an audit tracker."},
159+
{"role":"user","content":prompt}]
160+
)
161+
return { "reply": resp.choices[0].message.content }
162+
except Exception as e:
163+
return { "reply": f"AI error: {e}" }

backend/requirements.txt

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
fastapi==0.115.0
2+
uvicorn==0.30.6
3+
sqlalchemy==2.0.32
4+
psycopg2-binary==2.9.9
5+
python-dotenv==1.0.1
6+
pydantic==2.9.2
7+
pydantic-settings==2.6.1
8+
passlib[bcrypt]==1.7.4
9+
python-multipart==0.0.9
10+
requests==2.32.3
11+
apscheduler==3.10.4
12+
email-validator==2.2.0
13+
openai==1.51.2
14+
pandas==2.2.3
15+
openpyxl==3.1.5

docker-compose.yml

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
version: '3.9'
2+
3+
services:
4+
db:
5+
image: postgres:15
6+
environment:
7+
POSTGRES_USER: fatuser
8+
POSTGRES_PASSWORD: fatpass
9+
POSTGRES_DB: fatdb
10+
ports:
11+
- "5432:5432"
12+
volumes:
13+
- pgdata:/var/lib/postgresql/data
14+
15+
backend:
16+
build:
17+
context: ./backend
18+
dockerfile: Dockerfile
19+
env_file:
20+
- ./backend/.env
21+
ports:
22+
- "8000:8000"
23+
depends_on:
24+
- db
25+
volumes:
26+
- ./backend/uploads:/app/uploads
27+
28+
frontend:
29+
build:
30+
context: ./frontend
31+
dockerfile: Dockerfile
32+
ports:
33+
- "3000:3000"
34+
environment:
35+
- VITE_API_URL=http://localhost:8000
36+
depends_on:
37+
- backend
38+
39+
volumes:
40+
pgdata:

frontend/Dockerfile

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
FROM node:20-alpine
2+
WORKDIR /app
3+
4+
COPY package*.json ./
5+
RUN npm install --legacy-peer-deps
6+
7+
COPY . .
8+
RUN npm run build
9+
RUN npm install -g serve
10+
EXPOSE 3000
11+
CMD ["serve", "-s", "dist"]

frontend/index.html

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<!doctype html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8" />
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
6+
<link rel="icon" type="image/png" href="/public/favicon.png">
7+
<title>FAT-EIBL – Edme Insurance</title>
8+
<style>
9+
:root {
10+
--brand: rgb(175, 157, 165);
11+
--ink: #0d1b2a;
12+
--bg: #ffffff;
13+
--card: #f6f9ff;
14+
}
15+
body { margin:0; font-family: Inter, system-ui, Arial; background:var(--bg); color:var(--ink);}
16+
.appbar { display:flex; align-items:center; gap:10px; padding:12px 16px; border-bottom:1px solid #e6eef9; position:sticky; top:0; background:#fff; }
17+
.appbar img { height:34px; }
18+
.brand { color:var(--brand); font-weight:800; }
19+
a { color: var(--brand); text-decoration:none; }
20+
nav a { margin-right:14px; }
21+
.container { max-width:1100px; margin:20px auto; padding:0 16px; }
22+
.btn { background: var(--brand); color:#fff; border:none; padding:10px 14px; border-radius:10px; cursor:pointer; }
23+
.card { background:var(--card); border:1px solid #e6eef9; border-radius:14px; padding:16px; }
24+
table { width:100%; border-collapse:collapse; }
25+
th, td { border-bottom:1px solid #eef3fb; padding:10px; text-align:left; }
26+
th { background:#f2f7ff; }
27+
.muted { color:#60738a; }
28+
</style>
29+
</head>
30+
<body>
31+
<div id="root"></div>
32+
<script type="module" src="/src/main.jsx"></script>
33+
</body>
34+
</html>

0 commit comments

Comments
 (0)