Thanks to visit codestin.com
Credit goes to www.scribd.com

0% found this document useful (0 votes)
2 views20 pages

Assembly Instructions: Backend

Uploaded by

sanket.bhimani
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
2 views20 pages

Assembly Instructions: Backend

Uploaded by

sanket.bhimani
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
You are on page 1/ 20

Excellent!

We’ll start by assembling the backend folder for your Complaint Management System.

🛠️Assembly Instructions
1. Copy each code block into a file exactly as named.
2. After all files are created, you can run the backend as described below.

## backend/app/main.py
python
CopyEdit
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware

from app.routers import auth, users, complaints, analytics


from app.database import engine, Base

app = FastAPI(title="Cochi Complaint Management System")

app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)

Base.metadata.create_all(bind=engine)

app.include_router(auth.router, prefix="/api/auth", tags=["auth"])


app.include_router(users.router, prefix="/api/users", tags=["users"])
app.include_router(complaints.router, prefix="/api/complaints",
tags=["complaints"])
app.include_router(analytics.router, prefix="/api/analytics",
tags=["analytics"])

## backend/app/database.py
python
CopyEdit
from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
import os
DATABASE_URL = os.getenv("DATABASE_URL",
"postgresql://cmsuser:cmspass@db:5432/cmsdb")

engine = create_engine(DATABASE_URL)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Base = declarative_base()

## backend/app/models.py
python
CopyEdit
from sqlalchemy import Column, Integer, String, DateTime, ForeignKey, Enum,
Text
from sqlalchemy.orm import relationship
from sqlalchemy.sql import func
from app.database import Base
import enum

class UserRole(str, enum.Enum):


CITIZEN = "citizen"
OFFICER = "officer"
MAINTENANCE = "maintenance"
ADMIN = "admin"

class User(Base):
__tablename__ = "users"
id = Column(Integer, primary_key=True, index=True)
name = Column(String, nullable=False)
email = Column(String, unique=True, index=True)
mobile = Column(String, unique=True)
role = Column(Enum(UserRole), nullable=False)
zone = Column(String, nullable=True)
password_hash = Column(String)
complaints = relationship("Complaint", back_populates="user")

class ComplaintStatus(str, enum.Enum):


REGISTERED = "registered"
ASSIGNED = "assigned"
IN_PROGRESS = "in_progress"
RESOLVED = "resolved"
CLOSED = "closed"
REOPENED = "reopened"

class Complaint(Base):
__tablename__ = "complaints"
id = Column(Integer, primary_key=True, index=True)
type = Column(String, nullable=False)
desc = Column(Text, nullable=True)
status = Column(Enum(ComplaintStatus), default=ComplaintStatus.REGISTERED)
geo_lat = Column(String)
geo_lon = Column(String)
photo = Column(String) # File path
created_at = Column(DateTime(timezone=True), server_default=func.now())
user_id = Column(Integer, ForeignKey("users.id"))
user = relationship("User", back_populates="complaints")
assigned_to = Column(Integer, ForeignKey("users.id"), nullable=True)

class StatusLog(Base):
__tablename__ = "status_logs"
id = Column(Integer, primary_key=True, index=True)
complaint_id = Column(Integer, ForeignKey("complaints.id"))
status = Column(Enum(ComplaintStatus))
updated_by = Column(Integer, ForeignKey("users.id"))
timestamp = Column(DateTime(timezone=True), server_default=func.now())

class Notification(Base):
__tablename__ = "notifications"
id = Column(Integer, primary_key=True, index=True)
user_id = Column(Integer, ForeignKey("users.id"))
message = Column(String)
is_read = Column(Integer, default=0)
created_at = Column(DateTime(timezone=True), server_default=func.now())

## backend/app/schemas.py
python
CopyEdit
from pydantic import BaseModel, EmailStr, Field
from typing import Optional, List
from datetime import datetime
import enum

class UserRole(str, enum.Enum):


CITIZEN = "citizen"
OFFICER = "officer"
MAINTENANCE = "maintenance"
ADMIN = "admin"

class UserBase(BaseModel):
name: str
email: EmailStr
mobile: str
role: UserRole
zone: Optional[str] = None

class UserCreate(UserBase):
password: str

class UserOut(UserBase):
id: int

class Config:
orm_mode = True

class ComplaintStatus(str, enum.Enum):


REGISTERED = "registered"
ASSIGNED = "assigned"
IN_PROGRESS = "in_progress"
RESOLVED = "resolved"
CLOSED = "closed"
REOPENED = "reopened"

class ComplaintBase(BaseModel):
type: str
desc: Optional[str] = None
geo_lat: Optional[str] = None
geo_lon: Optional[str] = None
photo: Optional[str] = None

class ComplaintCreate(ComplaintBase):
pass

class ComplaintOut(ComplaintBase):
id: int
status: ComplaintStatus
created_at: datetime
user_id: int
assigned_to: Optional[int]

class Config:
orm_mode = True

## backend/app/routers/__init__.py
python
CopyEdit
# Empty file for Python package

## backend/app/routers/auth.py
python
CopyEdit
from fastapi import APIRouter, Depends, HTTPException, status
from pydantic import BaseModel, EmailStr
from app.models import User, UserRole
from app.schemas import UserOut
from sqlalchemy.orm import Session
from app.database import SessionLocal
import secrets

router = APIRouter()

otp_store = {} # Simple in-memory OTP store

class OTPRequest(BaseModel):
email: EmailStr

class OTPVerify(BaseModel):
email: EmailStr
otp: str

class TokenOut(BaseModel):
access_token: str
token_type: str = "bearer"

def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()

@router.post("/otp/send")
def send_otp(data: OTPRequest, db: Session = Depends(get_db)):
otp = f"{secrets.randbelow(1000000):06d}"
otp_store[data.email] = otp
print(f"** Simulated OTP for {data.email}: {otp} **")
return {"detail": f"OTP sent to {data.email} (simulated)."}

@router.post("/otp/verify", response_model=TokenOut)
def verify_otp(data: OTPVerify, db: Session = Depends(get_db)):
otp_expected = otp_store.get(data.email)
if not otp_expected or data.otp != otp_expected:
raise HTTPException(status_code=400, detail="Invalid OTP")
user = db.query(User).filter(User.email == data.email).first()
if not user:
user = User(
name=data.email.split("@")[0],
email=data.email,
mobile="",
role=UserRole.CITIZEN,
password_hash=""
)
db.add(user)
db.commit()
db.refresh(user)
token = "mocked.jwt.token" # Replace with real JWT
return {"access_token": token}

## backend/app/routers/users.py
python
CopyEdit
from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.orm import Session
from app.database import SessionLocal
from app.models import User, UserRole
from app.schemas import UserOut, UserCreate

router = APIRouter()

def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()

@router.post("/", response_model=UserOut)
def create_user(user: UserCreate, db: Session = Depends(get_db)):
db_user = db.query(User).filter(User.email == user.email).first()
if db_user:
raise HTTPException(status_code=400, detail="Email already
registered")
new_user = User(
name=user.name,
email=user.email,
mobile=user.mobile,
role=user.role,
password_hash=user.password # Replace with hash in real usage
)
db.add(new_user)
db.commit()
db.refresh(new_user)
return new_user

@router.get("/", response_model=list[UserOut])
def list_users(db: Session = Depends(get_db)):
return db.query(User).all()

## backend/app/routers/complaints.py
python
CopyEdit
from fastapi import APIRouter, Depends, HTTPException, UploadFile, File, Form
from sqlalchemy.orm import Session
from app.database import SessionLocal
from app.models import Complaint, ComplaintStatus, User
from app.schemas import ComplaintCreate, ComplaintOut
import os
from datetime import datetime

router = APIRouter()

def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()

UPLOAD_DIR = "uploads"
os.makedirs(UPLOAD_DIR, exist_ok=True)

@router.post("/", response_model=ComplaintOut)
def create_complaint(
type: str = Form(...),
desc: str = Form(""),
geo_lat: str = Form(""),
geo_lon: str = Form(""),
user_id: int = Form(...),
photo: UploadFile = File(None),
db: Session = Depends(get_db)
):
photo_path = None
if photo:
fname = f"{datetime.utcnow().timestamp()}_{photo.filename}"
fpath = os.path.join(UPLOAD_DIR, fname)
with open(fpath, "wb") as f:
f.write(photo.file.read())
photo_path = fpath
complaint = Complaint(
type=type,
desc=desc,
geo_lat=geo_lat,
geo_lon=geo_lon,
user_id=user_id,
status=ComplaintStatus.REGISTERED,
photo=photo_path
)
db.add(complaint)
db.commit()
db.refresh(complaint)
return complaint

@router.get("/", response_model=list[ComplaintOut])
def list_complaints(db: Session = Depends(get_db)):
return db.query(Complaint).all()

@router.get("/user/{user_id}", response_model=list[ComplaintOut])
def list_my_complaints(user_id: int, db: Session = Depends(get_db)):
return db.query(Complaint).filter(Complaint.user_id == user_id).all()

## backend/app/routers/analytics.py
python
CopyEdit
from fastapi import APIRouter, Depends
from sqlalchemy.orm import Session
from app.database import SessionLocal
from app.models import Complaint, ComplaintStatus
from datetime import datetime, timedelta

router = APIRouter()

def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
@router.get("/dashboard")
def dashboard(db: Session = Depends(get_db)):
total = db.query(Complaint).count()
registered = db.query(Complaint).filter(Complaint.status ==
ComplaintStatus.REGISTERED).count()
inprogress = db.query(Complaint).filter(Complaint.status ==
ComplaintStatus.IN_PROGRESS).count()
resolved = db.query(Complaint).filter(Complaint.status ==
ComplaintStatus.RESOLVED).count()
closed = db.query(Complaint).filter(Complaint.status ==
ComplaintStatus.CLOSED).count()
return {
"total": total,
"registered": registered,
"inprogress": inprogress,
"resolved": resolved,
"closed": closed
}

@router.get("/trends")
def complaint_trends(db: Session = Depends(get_db)):
now = datetime.utcnow()
result = []
for i in range(7):
day = now - timedelta(days=i)
count = db.query(Complaint).filter(
Complaint.created_at >= day.replace(hour=0, minute=0, second=0,
microsecond=0),
Complaint.created_at < (day + timedelta(days=1)).replace(hour=0,
minute=0, second=0, microsecond=0)
).count()
result.append({"date": day.strftime("%Y-%m-%d"), "count": count})
return list(reversed(result))

## backend/app/requirements.txt
css
CopyEdit
fastapi
uvicorn[standard]
SQLAlchemy
psycopg2-binary
pydantic
python-multipart

## backend/Dockerfile
sql
CopyEdit
FROM python:3.11
WORKDIR /app
COPY ./app /app/app
COPY requirements.txt .
RUN pip install --upgrade pip && pip install -r requirements.txt
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]

## backend/app/utils/__init__.py
python
CopyEdit
# empty

Create these folders:

 backend/app/routers/
 backend/app/utils/
 backend/app/
 backend/

Once you have these files, your FastAPI backend is ready!

 Run with:

bash
CopyEdit
cd backend
uvicorn app.main:app --reload

Reply "Next" to get the frontend (Next.js) full code in a similar format!
(And finally, docker-compose and README.)

You said:

Next
ChatGPT said:

Awesome! Let’s assemble the frontend (Next.js, TypeScript, Tailwind) for your Complaint
Management System.

Frontend Directory Structure

You’ll need:
pgsql
CopyEdit
frontend/
├── pages/
│ ├── _app.tsx
│ ├── index.tsx
│ ├── dashboard.tsx
│ └── complaints/
│ ├── new.tsx
│ └── [id].tsx
├── components/
│ ├── ComplaintList.tsx
│ ├── ComplaintMap.tsx
│ ├── LanguageSwitcher.tsx
│ └── Navbar.tsx
├── public/
├── styles/
│ └── globals.css
├── package.json
├── tailwind.config.js
├── tsconfig.json
└── Dockerfile

Create Folders
bash
CopyEdit
mkdir -p frontend/pages/complaints
mkdir frontend/components
mkdir frontend/public
mkdir frontend/styles

## frontend/package.json
json
CopyEdit
{
"name": "ccms-frontend",
"version": "1.0.0",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint"
},
"dependencies": {
"axios": "^1.6.2",
"next": "14.1.0",
"react": "18.2.0",
"react-dom": "18.2.0",
"tailwindcss": "^3.4.1"
},
"devDependencies": {
"@types/node": "20.10.5",
"@types/react": "18.2.43",
"autoprefixer": "10.4.14",
"postcss": "8.4.32",
"typescript": "5.4.5"
}
}

## frontend/tailwind.config.js
js
CopyEdit
/** @type {import('tailwindcss').Config} */
module.exports = {
content: [
"./pages/**/*.{js,ts,jsx,tsx}",
"./components/**/*.{js,ts,jsx,tsx}",
],
theme: {
extend: {},
},
plugins: [],
}

## frontend/styles/globals.css
css
CopyEdit
@tailwind base;
@tailwind components;
@tailwind utilities;

## frontend/tsconfig.json
json
CopyEdit
{
"compilerOptions": {
"target": "es5",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve"
},
"include": [
"next-env.d.ts",
"**/*.ts",
"**/*.tsx"
],
"exclude": [
"node_modules"
]
}

## frontend/Dockerfile
sql
CopyEdit
FROM node:18
WORKDIR /app
COPY . .
RUN npm install
RUN npm run build
EXPOSE 3000
CMD ["npm", "start"]

## frontend/pages/_app.tsx
tsx
CopyEdit
import "../styles/globals.css";
import type { AppProps } from "next/app";

export default function App({ Component, pageProps }: AppProps) {


return <Component {...pageProps} />;
}

## frontend/pages/index.tsx
tsx
CopyEdit
import Link from "next/link";
import LanguageSwitcher from "../components/LanguageSwitcher";

export default function Home() {


return (
<div className="flex flex-col items-center justify-center min-h-screen p-
4">
<h1 className="text-3xl font-bold mb-4">Complaint Management System</h1>
<div className="space-x-4 mb-8">
<Link href="/dashboard" className="btn-primary">Dashboard</Link>
<Link href="/complaints/new" className="btn-secondary">New
Complaint</Link>
</div>
<LanguageSwitcher />
</div>
);
}

## frontend/pages/dashboard.tsx
tsx
CopyEdit
import ComplaintList from "../components/ComplaintList";
import ComplaintMap from "../components/ComplaintMap";

export default function Dashboard() {


return (
<div className="p-4">
<h2 className="text-2xl font-semibold mb-4">Dashboard</h2>
<div className="grid md:grid-cols-2 gap-4">
<ComplaintList />
<ComplaintMap />
</div>
</div>
);
}

## frontend/pages/complaints/new.tsx
tsx
CopyEdit
import { useState } from "react";
import axios from "axios";

export default function NewComplaint() {


const [type, setType] = useState("");
const [desc, setDesc] = useState("");
const [file, setFile] = useState<File | null>(null);
const [message, setMessage] = useState("");
const [geo, setGeo] = useState({ lat: "", lon: "" });

const getLocation = () => {


if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(pos => {
setGeo({ lat: pos.coords.latitude + "", lon: pos.coords.longitude + ""
});
});
}
};
const handleSubmit = async (e: any) => {
e.preventDefault();
const form = new FormData();
form.append("type", type);
form.append("desc", desc);
form.append("geo_lat", geo.lat);
form.append("geo_lon", geo.lon);
form.append("user_id", "1"); // replace with actual user id from auth
if (file) form.append("photo", file);

try {
await axios.post("/api/complaints", form, { baseURL:
process.env.NEXT_PUBLIC_API_URL });
setMessage("Complaint submitted!");
} catch (e) {
setMessage("Error submitting complaint.");
}
};

return (
<div className="max-w-xl mx-auto p-6">
<h2 className="text-xl font-bold mb-4">Register a Complaint</h2>
<form onSubmit={handleSubmit} className="space-y-4">
<select value={type} onChange={e => setType(e.target.value)} required
className="input">
<option value="">Select Type</option>
<option value="Light Not Working">Light Not Working</option>
<option value="Flickering">Flickering</option>
<option value="Pole Damaged">Pole Damaged</option>
</select>
<textarea
className="input"
placeholder="Description"
value={desc}
onChange={e => setDesc(e.target.value)}
/>
<input type="file" onChange={e => setFile(e.target.files![0])} />
<button type="button" onClick={getLocation} className="btn-
secondary">Auto-Fill Location</button>
<button type="submit" className="btn-primary">Submit</button>
{message && <p>{message}</p>}
</form>
</div>
);
}

## frontend/components/ComplaintList.tsx
tsx
CopyEdit
import { useEffect, useState } from "react";
import axios from "axios";
export default function ComplaintList() {
const [complaints, setComplaints] = useState<any[]>([]);
useEffect(() => {
axios.get("/api/complaints", { baseURL:
process.env.NEXT_PUBLIC_API_URL }).then(res => {
setComplaints(res.data);
});
}, []);

return (
<div>
<h3 className="text-lg font-semibold mb-2">Complaints</h3>
<ul>
{complaints.map(c => (
<li key={c.id} className="p-2 border-b">
<b>{c.type}</b> - {c.status}
<div className="text-sm text-gray-500">{c.desc}</div>
</li>
))}
</ul>
</div>
);
}

## frontend/components/ComplaintMap.tsx
tsx
CopyEdit
import { useEffect, useState } from "react";
import axios from "axios";

export default function ComplaintMap() {


const [complaints, setComplaints] = useState<any[]>([]);
useEffect(() => {
axios.get("/api/complaints", { baseURL:
process.env.NEXT_PUBLIC_API_URL }).then(res => {
setComplaints(res.data);
});
}, []);
// You can add a map (e.g., using Leaflet) here for real geo view.
return (
<div className="bg-gray-100 p-4 rounded">
<h3 className="font-bold mb-2">Map View (Stub)</h3>
<ul>
{complaints.map(c => (
<li key={c.id}>
{c.type} @ {c.geo_lat},{c.geo_lon}
</li>
))}
</ul>
</div>
);
}
## frontend/components/LanguageSwitcher.tsx
tsx
CopyEdit
export default function LanguageSwitcher() {
return (
<div className="flex gap-2 mt-4">
<button className="btn-secondary">English</button>
<button className="btn-secondary">മലയാളം</button>
<button className="btn-secondary">हिन्दी</button>
</div>
);
}

## frontend/components/Navbar.tsx
tsx
CopyEdit
import Link from "next/link";
export default function Navbar() {
return (
<nav className="bg-blue-700 text-white px-4 py-2 flex gap-4">
<Link href="/">Home</Link>
<Link href="/dashboard">Dashboard</Link>
<Link href="/complaints/new">New Complaint</Link>
</nav>
);
}

Frontend Setup
1. Create all files as above.
2. Run these in frontend/:

bash
CopyEdit
npm install
npm run dev

(Or use Docker for prod.)

Reply “Next” for docker-compose.yml and README.md!

You said:
Next
ChatGPT said:

Great! Here are the final pieces: docker-compose.yml and README.md to tie the backend and
frontend together and make your Complaint Management System easy to run.

## docker-compose.yml
yaml
CopyEdit
version: '3.8'
services:
db:
image: postgres:15
restart: always
environment:
POSTGRES_USER: cmsuser
POSTGRES_PASSWORD: cmspass
POSTGRES_DB: cmsdb
ports:
- "5432:5432"
volumes:
- pgdata:/var/lib/postgresql/data
backend:
build: ./backend
environment:
- DATABASE_URL=postgresql://cmsuser:cmspass@db:5432/cmsdb
ports:
- "8000:8000"
depends_on:
- db
volumes:
- ./backend/app/uploads:/app/app/uploads
frontend:
build: ./frontend
ports:
- "3000:3000"
environment:
- NEXT_PUBLIC_API_URL=http://localhost:8000/api
depends_on:
- backend
volumes:
pgdata:

## README.md
markdown
CopyEdit
# Cochi Complaint Management System (`ccms-app`)
A full-stack web app for complaint registration, management, and analytics, as
part of the Smart City initiative.

---

## Features

- Citizen registration/login (OTP-based)


- Role-based dashboard (Citizen, Officer, Maintenance, Admin)
- Complaint creation with geolocation, photo upload, and status tracking
- Assignment and status update by staff
- Dashboard, heatmap, and analytics
- Multilingual-ready (English, Malayalam, Hindi)
- Fully responsive

---

## Quick Start (with Docker)

### 1. Prerequisites
- [Docker & Docker Compose](https://docs.docker.com/compose/install/)
installed

### 2. Folder Structure

ccms-app/
backend/
app/
... (Python code here)
frontend/
... (Next.js code here)
docker-compose.yml
README.md

perl
CopyEdit

### 3. Build and Run

```bash
docker-compose up --build

 Backend API: http://localhost:8000/docs


 Frontend: http://localhost:3000

4. First Login

 Use the OTP flow to create a new user, or create admin via the /api/users endpoint.
Development (manual, without Docker)
 Backend:

bash
CopyEdit
cd backend
pip install -r requirements.txt
uvicorn app.main:app --reload

 Frontend:

bash
CopyEdit
cd frontend
npm install
npm run dev

Database Access
 Username: cmsuser
 Password: cmspass
 DB: cmsdb (port 5432)

Customization & Notes


 Change CORS and API URLs as needed for production.
 To enable email OTP or real notifications, connect an SMTP service in backend.
 File uploads are stored locally by default (backend/app/uploads).
 For production: Set up a reverse proxy (Nginx), HTTPS, and adjust DB connection pool.

Questions or need support?


Feel free to ask!

yaml
CopyEdit

---

## 🚀 **You are now ready to run the complete stack!**

1. **Backend:** FastAPI with PostgreSQL, file uploads, and JWT/OTP


(simulated).
2. **Frontend:** Next.js with Tailwind, role-based dashboard, multilingual-
ready.
3. **Run everything:** `docker-compose up --build`
4. **Access frontend at:** [http://localhost:3000](http://localhost:3000)
5. **Access API at:** [http://localhost:8000/docs](http://localhost:8000/docs)

---

**If you want a quick bash script to create the folder structure and place all
files, let me know!**
Or, if you need a ZIP and can provide a cloud share/email, I can send it
there.

You’re all set! If you need anything else, just ask! 🚦

You might also like