Project built around a law-firm operations dataset using:
MySQL 8.xas the databaseStored procedures,stored functions,cursor procedures,triggers,views, and joined SQL reportsFastAPIas the backend API layerReact + Vite + Tailwindas the frontend interface
The project models legal matters, clients, employees, access control, hearings, billing, documents, support tickets, conflicts, and audit logs. The database layer is now the main focus of the system.
This project is designed to show a DBMS-oriented implementation of a legal-management system:
- manage cases and their teams
- track client relationships and interactions
- record documents and document versions
- control matter access and conflict checks
- manage billing approvals
- track support tickets and SLA risk
- maintain auditability through logs and triggers
The schema lives in backend/sql/schema.sql and contains:
- normalized master tables such as
Department,Role,Employee,Client,Court,Permission - transactional tables such as
Cases,Case_Team,Hearing,Document,Billing,Time_Log,Ticket - control and audit tables such as
Access_Control,Conflict_Check,Audit_Log,Ticket_Logs,IT_System_Log - foreign keys, check constraints, and performance-oriented indexes
The stored-programming layer lives in backend/sql/procedures.sql. The Systems Oversight support layer lives in backend/sql/dbms_features.sql so activity logging, protected-record controls, continuity snapshots, stored functions, cursor report tables, and operational views are easy to review in one file.
create_case_fullassign_employee_caseapprove_billingraise_ticketresolve_ticket_workflow
generate_client_billing_reportgenerate_ticket_sla_reviewsp_generate_case_reportsp_generate_employee_workload_reportsp_generate_ticket_report
These are included specifically so the project clearly demonstrates cursor usage in MySQL stored programming.
check_accessget_employee_access_levelget_case_billed_totalget_case_total_hoursfn_total_case_billingfn_total_case_hoursfn_employee_case_countfn_ticket_sla_status
The /dbms frontend page appears as Systems Oversight and is backed by synthetic operational trace rows seeded from backend/sql/sample_data.sql. It shows:
- protected records from
Lock_Log - interrupted and resolved activity from
Transaction_Log - continuity snapshot history from
System_Checkpoint - generated cursor reports for cases, employees, and tickets
The trigger layer lives in backend/sql/triggers.sql.
Implemented trigger workflows include:
- conflict blocking before case-team assignment
- automatic case status history logging
- employee update auditing
- billing approval validation
- SLA breach marking on ticket resolution
- automatic initial document version creation
- ticket update logging
- time-log approval and hours validation
The reporting/view layer lives in backend/sql/views_reports.sql.
Important views include:
vw_employee_directoryvw_role_access_matrixvw_case_overviewvw_case_team_rostervw_hearing_calendarvw_document_registervw_billing_registervw_ticket_overviewvw_client_portfoliovw_conflict_registervw_audit_trail
The manual report script lives in backend/sql/tabular_reports.sql.
It prints joined tabular output for:
- employees
- roles and permissions
- clients
- cases
- case teams
- hearings
- documents
- billing
- time logs
- tickets
- ticket logs
- access control
- conflicts
- audit logs
- IT logs
It also demonstrates the cursor procedures directly using:
CALL generate_client_billing_report(1);
CALL generate_ticket_sla_review(3);backend/
app/
main.py
db.py
config.py
routes/
services/
sql/
schema.sql
access_control.sql
dbms_features.sql
triggers.sql
procedures.sql
views_reports.sql
sample_data.sql
access_dashboard.sql
tabular_reports.sql
init_db.ps1
dbms_lab/
01_plsql_basics.sql ... 10_dashboard_queries.sql
DBMS_Features.md
frontend/
src/
uploads/
Copy .env.example to .env or backend/.env and set the MySQL credentials:
DB_HOST=localhost
DB_PORT=3306
DB_NAME=lawfirm
DB_USER=root
DB_PASSWORD=your_password_hereFrom the repository root:
powershell -ExecutionPolicy Bypass -File .\backend\sql\init_db.ps1 -ResetDatabase -IncludeSampleDataUseful variations:
powershell -ExecutionPolicy Bypass -File .\backend\sql\init_db.ps1 -User root
powershell -ExecutionPolicy Bypass -File .\backend\sql\init_db.ps1 -ServerHost localhost
powershell -ExecutionPolicy Bypass -File .\backend\sql\init_db.ps1 -MysqlPath "C:\Program Files\MySQL\MySQL Server 8.0\bin\mysql.exe"
powershell -ExecutionPolicy Bypass -File .\backend\sql\init_db.ps1 -ResetDatabaseWhen sample data is included, the script also creates placeholder files inside /uploads so seeded document rows have matching local file names.
If you want to run the SQL files manually, use this order:
SOURCE backend/sql/schema.sql;
SOURCE backend/sql/access_control.sql;
SOURCE backend/sql/dbms_features.sql;
SOURCE backend/sql/procedures.sql;
SOURCE backend/sql/triggers.sql;
SOURCE backend/sql/views_reports.sql;
SOURCE backend/sql/sample_data.sql;To print the joined report output after initialization:
SOURCE backend/sql/tabular_reports.sql;Install backend dependencies:
python -m pip install -r .\backend\requirements.txtRun the API:
uvicorn backend.app.main:app --reloadMain routes:
GET /GET /healthGET /overviewGET /analyticsGET /casesGET /cases/{case_id}GET /cases/{case_id}/teamPOST /cases/{case_id}/teamGET /cases/{case_id}/documentsGET /cases/{case_id}/status-historyGET /cases/{case_id}/billingPOST /billing/{bill_id}/approveGET /clientsPOST /clientsGET /documentsGET /ticketsPOST /ticketsPOST /tickets/{ticket_id}/resolveGET /employeesGET /rolesPOST /upload-document/
Open http://127.0.0.1:8000/docs for the interactive API docs.
Install dependencies from the repository root:
npm installRun the frontend:
npm run devBuild the frontend:
npm run buildBy default, the frontend calls the same origin as the page. In local Vite dev,
frontend/vite.config.ts proxies API paths to http://127.0.0.1:8000.
Override it with VITE_API_BASE_URL if you deploy the frontend and backend as
separate services.
This repository includes a Dockerfile for Railway. It builds the React frontend,
installs the FastAPI backend, and serves both from one public Railway URL.
In Railway service settings, deploy from the repository root (/). Do not set
the root directory to frontend, because that only serves the React app and API
routes such as /health, /overview, and /clients will return index.html.
Required Railway variables:
DB_HOST=your-mysql-host
DB_PORT=3306
DB_NAME=lawfirm
DB_USER=your-mysql-user
DB_PASSWORD=your-mysql-passwordRailway MySQL variables named MYSQLHOST, MYSQLPORT, MYSQLDATABASE,
MYSQLUSER, and MYSQLPASSWORD also work. URL-style variables named
MYSQL_URL, MYSQL_PUBLIC_URL, or DATABASE_URL also work. After deployment,
open:
https://your-app.up.railway.app/health
It should return "database": "reachable". If the frontend loads but shows
zero counts, the app is running but the database is either empty or not connected
to the seeded lawfirm schema.
This repository also includes root deployment metadata for Vercel's FastAPI
builder. app.py exports the existing FastAPI application from
backend.app.main:app, while pyproject.toml runs npm run build so the React
bundle exists before the backend starts serving requests.
Deploy from the repository root (/). Keep the Vercel framework preset as
FastAPI, or let Vercel detect it from pyproject.toml.
Required Vercel variables:
DB_HOST=your-mysql-host
DB_PORT=3306
DB_NAME=lawfirm
DB_USER=your-mysql-user
DB_PASSWORD=your-mysql-passwordURL-style variables named MYSQL_URL, MYSQL_PUBLIC_URL, or DATABASE_URL
also work. After deployment, open:
https://your-vercel-app.vercel.app/health
It should return "database": "reachable".
POST /cases calls the stored procedure create_case_full, which:
- inserts the case
- inserts the initial case status history row
- auto-assigns the lead partner and lead senior into
Case_Team
POST /cases/{case_id}/team calls assign_employee_case, which:
- inserts the team assignment
- records related access in
Access_Control - still passes through the conflict-check trigger
POST /billing/{bill_id}/approve calls approve_billing, which:
- marks the bill as approved
- records the approver
- writes an audit entry
- is still validated by the approval trigger
POST /tickets calls raise_ticket.
POST /tickets/{ticket_id}/resolve calls resolve_ticket_workflow.
This moves ticket business rules into MySQL so permissions, status change handling, and SLA-related logging are not only enforced in Python.
When a new document row is inserted:
- the document is stored in
Document - the trigger automatically creates version
1inDocument_Version