Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
venv
__pycache__
8 changes: 3 additions & 5 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@ FROM python:3.11-slim
# Set working directory
WORKDIR /app

# Install system dependencies for psycopg2
# Install system dependencies for mysql-connector-python and tkinter
RUN apt-get update && apt-get install -y \
gcc libpq-dev && \
gcc libmariadb-dev pkg-config mariadb-client \
python3-tk xvfb && \
rm -rf /var/lib/apt/lists/*

# Install Python dependencies
Expand All @@ -14,6 +15,3 @@ RUN pip install --no-cache-dir -r requirements.txt

# Copy source code
COPY . .

CMD ["python", "app.py"]

49 changes: 38 additions & 11 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,34 +1,61 @@
PROJECT_NAME=postgres_service
PROJECT_NAME=mysql_service
COMPOSE=docker-compose -p $(PROJECT_NAME)

## up: Start the postgres and app containers in the background
# Check if Docker is running
.PHONY: check-docker
check-docker:
@docker info >nul 2>&1 || (echo. && echo ERROR: Docker is not running. Please start Docker Desktop and try again. && echo. && exit /b 1)

## up: Start the mysql, api (port 5000), and api-test (port 5001) containers and seed default locations
.PHONY: up
up:
up: check-docker
$(COMPOSE) up -d
@echo "Waiting for services to be ready..."
@timeout /t 5 /nobreak >nul 2>&1 || sleep 5 2>/dev/null || true
@$(COMPOSE) exec api python src/scripts/seed_locations.py

## up-empty: Start the mysql, api (port 5000), and api-test (port 5001) containers without seeding data
.PHONY: up-empty
up-empty: check-docker
$(COMPOSE) up -d

## down: Stop and remove containers (keeps volumes)
.PHONY: down
down:
down: check-docker
$(COMPOSE) down

## status: Show status of all services
.PHONY: status
status: check-docker
$(COMPOSE) ps

## logs: Follow logs from all services
.PHONY: logs
logs:
logs: check-docker
$(COMPOSE) logs -f

## psql: Open a psql shell into the postgres container
.PHONY: psql
psql:
$(COMPOSE) exec db psql -U postgres -d mydb
## mysql: Open a mysql shell into the mysql container
.PHONY: mysql
mysql: check-docker
$(COMPOSE) exec db mysql -u mysqluser -pmysqlpassword mydb

## test: Open GUI test runner (starts services if not running, including test API on port 5001)
.PHONY: test
test: check-docker
@echo "Ensuring services are running..."
@$(COMPOSE) up -d
@echo "Waiting for services to be ready..."
@timeout /t 5 /nobreak >nul 2>&1 || sleep 5 2>/dev/null || true
@python src/scripts/test_gui.py

## build: Build or rebuild services
.PHONY: build
build:
build: check-docker
$(COMPOSE) build

## clean: Remove containers, networks, volumes, and orphans
.PHONY: clean
clean:
clean: check-docker
$(COMPOSE) down -v --remove-orphans
docker volume prune -f

Expand Down
174 changes: 172 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
# PostgreSQL Service Setup
# Mil-SQL API Service

This project provides a simple Dockerized PostgreSQL database and a Python service for interacting with it.
This project provides a Dockerized MySQL database with a Flask REST API for managing locations and supplies inventory.

---

## Requirements
- Docker (>= 20.10)
- Docker Compose (v2 recommended)
- Make (optional, for convenience)
- Python 3.11+ (for local test GUI)

---

Expand All @@ -17,3 +18,172 @@ This project provides a simple Dockerized PostgreSQL database and a Python servi
```bash
make up
```

This will:
- Start a MySQL 8.0 database container
- Start a Flask API container on port 5000 (production database: `mydb`)
- Start a Flask API container on port 5001 (test database: `mydb_test`)
- Automatically initialize the database schema for both APIs (idempotent - safe to run multiple times)

### 2. Run tests
```bash
make test
```

Opens a GUI test runner that allows you to:
- Validate table creation
- Test locations and supplies data insertion
- Interactively move supplies between containers

---

## API Endpoints

The Flask API runs on two ports:
- **Production API**: `http://localhost:5000` (database: `mydb`)
- **Test API**: `http://localhost:5001` (database: `mydb_test`)

Both APIs provide the same endpoints:

### Locations
- `GET /api/locations` - Get all locations
- `GET /api/locations/<name>` - Get a specific location
- `POST /api/locations` - Create a new location
- `PUT /api/locations/<name>` - Update a location
- `DELETE /api/locations/<name>` - Delete a location

### Supplies
- `GET /api/supplies` - Get all supplies (optional `?location=<name>` filter)
- `GET /api/supplies/<id>` - Get a specific supply
- `POST /api/supplies` - Create/update a supply (adds to existing if same name+location)
- `PUT /api/supplies/<id>` - Update supply amount or last_order_date
- `DELETE /api/supplies/<id>` - Delete a supply
- `POST /api/supplies/move` - Move supplies between locations

### Health Check
- `GET /health` - API health status

---

## Database Initialization

The Flask API (`src/api/app.py`) handles database schema initialization automatically on startup. It:

1. **Creates tables** in the correct dependency order (idempotent)
2. **Only creates tables that don't exist** - safe to run multiple times
3. **Never drops existing data** - production-safe
4. **Handles dependencies** - automatically sorts tables by foreign key relationships

### Environment Variables

| Variable | Default | Description |
|----------|---------|-------------|
| `DATABASE_URL` | `mysql://mysqluser:mysqlpassword@db:3306/mydb` | MySQL connection string |
| `DB_HOST` | `db` | Database hostname |
| `DB_PORT` | `3306` | Database port |
| `DB_USER` | `mysqluser` | Database username |
| `DB_PASSWORD` | `mysqlpassword` | Database password |
| `DB_NAME` | `mydb` | Database name |
| `PORT` | `5000` | Flask API port |
| `MYSQL_ROOT_PASSWORD` | `rootpassword` | MySQL root password |

---

## Testing

### Test API

A separate test API instance runs on port 5001 and connects to the `mydb_test` database. This allows test scripts to:
- Use the API endpoints without affecting production data
- Test API functionality in isolation
- Access the test database through the same API interface as production

Test scripts can use the `TEST_API_URL` constant from `helpers.py`:
```python
from helpers import TEST_API_URL
import requests

# Use test API (http://localhost:5001/api)
response = requests.get(f"{TEST_API_URL}/locations")
```

### Test Scripts

- **`test_tables.py`** - Validates table creation in a test database
- **`test_locations_supplies.py`** - Tests locations and supplies with interactive GUI
- **`test_gui.py`** - GUI test runner that discovers and runs all test scripts

### Running Tests

```bash
make test
```

This opens a GUI window where you can:
- Run individual tests
- Run all tests
- View test results in real-time
- See table contents in interactive viewers

**Note**: The test API (`api-test` service) starts automatically with `make up` or `make test` and connects to the `mydb_test` database.

---

## Database Schema

The database includes the following tables:
- `teams` - Team definitions
- `locations` - Storage locations (with coordinates for frontend positioning)
- `members` - Team members
- `weekly_reports` - Member progress reports
- `supplies` - Inventory items (with unique constraint on name+location)
- `orders` - Purchase orders
- `applicants` - Applicant information

### Key Features

- **Supplies normalization**: Each supply can exist in multiple locations with different amounts
- **Unique constraint**: `(name, location)` ensures no duplicate supply entries per location
- **Foreign keys**: Supplies reference locations, maintaining referential integrity

See `src/sql/` for table definitions.

---

## Available Commands

| Command | Description |
|---------|-------------|
| `make up` | Start all services |
| `make down` | Stop all services |
| `make build` | Build or rebuild services |
| `make test` | Run GUI test runner |
| `make mysql` | Open MySQL shell |
| `make logs` | View service logs |
| `make status` | Show service status |
| `make clean` | Remove containers, volumes, and networks |

---

## Project Structure

```
mil-sql/
├── src/
│ ├── api/ # Flask API application
│ │ ├── app.py # Main Flask app
│ │ ├── db.py # Database connection pool
│ │ ├── models/ # Data models
│ │ └── routes/ # API route handlers
│ ├── scripts/ # Utility and test scripts
│ │ ├── helpers.py # Shared database helpers
│ │ ├── test_gui.py # GUI test runner
│ │ ├── test_tables.py
│ │ └── test_locations_supplies.py
│ └── sql/ # SQL table definitions
│ └── */table_*.sql
├── docker-compose.yaml # Service definitions
├── Dockerfile # Python API container
├── requirements.txt # Python dependencies
└── Makefile # Convenience commands
```
65 changes: 49 additions & 16 deletions docker-compose.yaml
Original file line number Diff line number Diff line change
@@ -1,31 +1,64 @@
version: "3.9"

services:
db:
image: postgres:16
container_name: postgres_db
image: mysql:8.0
container_name: mysql_db
restart: unless-stopped
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: mydb
MYSQL_ROOT_PASSWORD: rootpassword
MYSQL_DATABASE: mydb
MYSQL_USER: mysqluser
MYSQL_PASSWORD: mysqlpassword
ports:
- "3306:3306"
volumes:
- db_data:/var/lib/mysql
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-prootpassword"]
interval: 10s
timeout: 5s
retries: 5

api:
build: .
container_name: mysql_api
depends_on:
db:
condition: service_healthy
environment:
DATABASE_URL: mysql://mysqluser:mysqlpassword@db:3306/mydb
DB_HOST: db
DB_PORT: 3306
DB_USER: mysqluser
DB_PASSWORD: mysqlpassword
DB_NAME: mydb
MYSQL_ROOT_PASSWORD: rootpassword
PORT: 5000
ports:
- "5432:5432"
- "5000:5000"
volumes:
- db_data:/var/lib/postgresql/data
- .:/app
command: ["python", "-m", "src.api.app"]

app:
api-test:
build: .
container_name: postgres_app
container_name: mysql_api_test
depends_on:
- db
db:
condition: service_healthy
environment:
DATABASE_URL: postgres://postgres:postgres@db:5432/mydb
DATABASE_URL: mysql://mysqluser:mysqlpassword@db:3306/mydb_test
DB_HOST: db
DB_PORT: 3306
DB_USER: mysqluser
DB_PASSWORD: mysqlpassword
DB_NAME: mydb_test
MYSQL_ROOT_PASSWORD: rootpassword
PORT: 5001
ports:
- "5001:5001"
volumes:
- .:/app
- ./src/sql:/docker-entrypoint-initb.d
command: ["python", "src/scripts/app.py"]
command: ["python", "-m", "src.api.app"]

volumes:
db_data:

5 changes: 4 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
psycopg2-binary==2.9.9
mysql-connector-python==8.2.0
sqlalchemy==2.0.30
alembic==1.13.2
flask==3.0.0
flask-cors==4.0.0
requests==2.31.0

2 changes: 2 additions & 0 deletions src/api/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# API package

Loading