Initial commit with version management

This commit is contained in:
colin 2025-07-03 10:37:02 -04:00
commit 375774594a
18 changed files with 1594 additions and 0 deletions

View File

@ -0,0 +1,10 @@
---
description:
globs:
alwaysApply: false
---
# Docker Organization
- Keep all source code adjacent to its corresponding Dockerfile
- Avoid separating application code from its Docker configuration
- Place related files in the same directory as the Dockerfile they support

View File

@ -0,0 +1,11 @@
---
description:
globs:
alwaysApply: false
---
# File Management
- Avoid creating new files whenever possible
- Modify existing files in place rather than creating copies
- Use git for version control instead of creating backup files
- Only create new files when absolutely necessary for new functionality

106
README.md Normal file
View File

@ -0,0 +1,106 @@
# Project Ploughshares
Transaction Management System for Project Ploughshares.
## Version
The current version is stored in the `VERSION` file. Use the `versionbump.sh` script to update the version number.
```bash
# To bump the patch version (e.g., 1.0.0 -> 1.0.1)
./versionbump.sh patch
# To bump the minor version (e.g., 1.0.0 -> 1.1.0)
./versionbump.sh minor
# To bump the major version (e.g., 1.0.0 -> 2.0.0)
./versionbump.sh major
```
## Docker Setup
The application is containerized using Docker and can be run using docker-compose.
```bash
# Build the containers
docker-compose build
# Start the application
docker-compose up
```
The application will be available at http://localhost:5001.
## Features
- Transaction management (create, view, edit)
- Document uploads and attachments
- API endpoints for programmatic access
- PostgreSQL database for data storage
## Running the Application
### Using Docker (Recommended)
The application can be run using Docker:
```bash
# Run with PostgreSQL database
docker-compose up --build
```
This will:
1. Build the Docker image
2. Start PostgreSQL database
3. Initialize the database schema
4. Start the application on port 5001
#### Stopping the Application
```bash
# Stop all containers
docker-compose down
```
### Running Locally
1. Start PostgreSQL:
```bash
./start_postgres.sh
```
2. Initialize the database:
```bash
python init_db.py
```
3. Start the application:
```bash
python app.py
```
## API Documentation
API documentation is available at:
- http://localhost:5001/api-docs
- http://localhost:5001/api/docs
- http://localhost:5001/docs
## Testing
To generate test data:
```bash
python generate_test_data.py
```
## Accessing the Application
The application runs on all addresses (0.0.0.0) and is accessible via:
- http://localhost:5001 (Docker)
- http://localhost:5001 (Local)
- http://<machine-ip>:5001 (Network access)
## License
This project is licensed under the MIT License - see the LICENSE file for details.

1
VERSION Normal file
View File

@ -0,0 +1 @@
1.0.0

44
docker-compose.yml Normal file
View File

@ -0,0 +1,44 @@
networks:
ploughshares-network:
driver: bridge
services:
app:
build:
context: ./docker/ploughshares
ports:
- "5001:5001"
environment:
- FLASK_RUN_PORT=5001
- POSTGRES_HOST=db
- POSTGRES_PORT=5432
- POSTGRES_DB=ploughshares
- POSTGRES_USER=ploughshares
- POSTGRES_PASSWORD=ploughshares_password
depends_on:
db:
condition: service_healthy
restart: unless-stopped
networks:
- ploughshares-network
db:
image: postgres:12
environment:
- POSTGRES_DB=ploughshares
- POSTGRES_USER=ploughshares
- POSTGRES_PASSWORD=ploughshares_password
volumes:
- postgres_data:/var/lib/postgresql/data
- ./docker/ploughshares/schema.sql:/docker-entrypoint-initdb.d/schema.sql
restart: unless-stopped
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ploughshares -d ploughshares"]
interval: 10s
timeout: 5s
retries: 5
networks:
- ploughshares-network
volumes:
postgres_data:

View File

@ -0,0 +1,30 @@
FROM python:3.9-bullseye
WORKDIR /app
# Install system dependencies
RUN apt-get update && apt-get install -y \
libpq-dev \
gcc \
postgresql-client \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*
# Copy requirements and install Python dependencies
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# Copy application code
COPY app.py .
COPY generate_test_data.py .
COPY schema.sql .
COPY templates/ ./templates/
# Create uploads directory
RUN mkdir -p uploads
# Expose the port the app runs on
EXPOSE 5001
# Command to run the application
CMD ["python", "app.py"]

233
docker/ploughshares/app.py Normal file
View File

@ -0,0 +1,233 @@
import os
import psycopg2
from psycopg2.extras import RealDictCursor
from flask import Flask, render_template, request, redirect, url_for, flash, jsonify, send_from_directory, abort
from werkzeug.utils import secure_filename
from datetime import datetime
import generate_test_data
import locale
# Get version from VERSION file
with open(os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), 'VERSION'), 'r') as f:
VERSION = f.read().strip()
# Initialize the Flask app
app = Flask(__name__)
app.secret_key = 'supersecretkey'
app.config['UPLOAD_FOLDER'] = 'uploads'
app.config['VERSION'] = VERSION
# Set locale for currency formatting
try:
locale.setlocale(locale.LC_ALL, 'en_US.UTF-8')
except locale.Error:
try:
locale.setlocale(locale.LC_ALL, '') # Use system default locale
except locale.Error:
pass # If all fails, we'll use the fallback in the filter
# Custom filter for currency formatting
@app.template_filter('currency')
def currency_filter(value):
if value is None:
return "$0.00"
try:
return locale.currency(float(value), grouping=True)
except (ValueError, TypeError, locale.Error):
# Fallback formatting if locale doesn't work
return f"${float(value):,.2f}"
# --- Database Connection ---
def get_db_connection():
host = os.environ.get('POSTGRES_HOST', 'db')
port = os.environ.get('POSTGRES_PORT', '5432')
dbname = os.environ.get('POSTGRES_DB', 'ploughshares')
user = os.environ.get('POSTGRES_USER', 'ploughshares')
password = os.environ.get('POSTGRES_PASSWORD', 'ploughshares_password')
try:
conn = psycopg2.connect(
host=host,
port=port,
dbname=dbname,
user=user,
password=password,
cursor_factory=RealDictCursor
)
conn.autocommit = True
print(f"Connected to PostgreSQL at {host}:{port}")
return conn
except psycopg2.OperationalError as e:
print(f"Error connecting to PostgreSQL: {e}")
return None
# --- Routes ---
@app.route('/')
def index():
conn = get_db_connection()
with conn.cursor() as cur:
cur.execute('SELECT * FROM transactions ORDER BY id DESC')
transactions = cur.fetchall()
conn.close()
return render_template('index.html', transactions=transactions, version=VERSION)
@app.route('/api-docs')
def api_docs():
server_name = request.host
return render_template('api_docs.html', server_name=server_name, version=VERSION)
@app.route('/transaction/<int:id>')
def view_transaction(id):
conn = get_db_connection()
with conn.cursor() as cur:
cur.execute('SELECT * FROM transactions WHERE id = %s', (id,))
transaction = cur.fetchone()
conn.close()
if transaction is None:
abort(404)
return render_template('view_transaction.html', transaction=transaction, version=VERSION)
@app.route('/transaction/add', methods=['GET', 'POST'])
def create_transaction():
if request.method == 'POST':
# Get form data and set defaults for missing fields
data = request.form.to_dict()
default_fields = {
'transaction_type': '', 'company_division': '', 'address_1': '', 'address_2': '',
'city': '', 'province': '', 'region': '', 'postal_code': '', 'source_date': None,
'source_description': '', 'grant_type': '', 'description': '', 'amount': 0,
'recipient': '', 'commodity_class': '', 'contract_number': '', 'comments': ''
}
# Fill in missing fields with defaults
for field, default in default_fields.items():
if field not in data:
data[field] = default
# Handle checkbox fields
data['is_primary'] = 'is_primary' in data
conn = get_db_connection()
with conn.cursor() as cur:
cur.execute(
"""
INSERT INTO transactions (
transaction_type, company_division, address_1, address_2,
city, province, region, postal_code, is_primary, source_date, source_description,
grant_type, description, amount, recipient, commodity_class, contract_number, comments
) VALUES (
%(transaction_type)s, %(company_division)s, %(address_1)s, %(address_2)s,
%(city)s, %(province)s, %(region)s, %(postal_code)s, %(is_primary)s, %(source_date)s,
%(source_description)s, %(grant_type)s, %(description)s, %(amount)s, %(recipient)s,
%(commodity_class)s, %(contract_number)s, %(comments)s
) RETURNING id
""",
data
)
new_transaction_id = cur.fetchone()['id']
conn.commit()
conn.close()
return redirect(url_for('view_transaction', id=new_transaction_id))
return render_template('transaction_form.html', version=VERSION)
@app.route('/transaction/<int:id>/edit', methods=['GET', 'POST'])
def update_transaction(id):
conn = get_db_connection()
if request.method == 'POST':
# Get form data and set defaults for missing fields
data = request.form.to_dict()
default_fields = {
'transaction_type': '', 'company_division': '', 'address_1': '', 'address_2': '',
'city': '', 'province': '', 'region': '', 'postal_code': '', 'source_date': None,
'source_description': '', 'grant_type': '', 'description': '', 'amount': 0,
'recipient': '', 'commodity_class': '', 'contract_number': '', 'comments': ''
}
# Fill in missing fields with defaults
for field, default in default_fields.items():
if field not in data:
data[field] = default
# Handle checkbox fields
data['is_primary'] = 'is_primary' in data
data['id'] = id
with conn.cursor() as cur:
cur.execute(
"""
UPDATE transactions
SET transaction_type = %(transaction_type)s,
company_division = %(company_division)s,
address_1 = %(address_1)s,
address_2 = %(address_2)s,
city = %(city)s,
province = %(province)s,
region = %(region)s,
postal_code = %(postal_code)s,
is_primary = %(is_primary)s,
source_date = %(source_date)s,
source_description = %(source_description)s,
grant_type = %(grant_type)s,
description = %(description)s,
amount = %(amount)s,
recipient = %(recipient)s,
commodity_class = %(commodity_class)s,
contract_number = %(contract_number)s,
comments = %(comments)s
WHERE id = %(id)s
""",
data
)
conn.commit()
conn.close()
return redirect(url_for('view_transaction', id=id))
with conn.cursor() as cur:
cur.execute('SELECT * FROM transactions WHERE id = %s', (id,))
transaction = cur.fetchone()
conn.close()
if transaction is None:
abort(404)
return render_template('transaction_form.html', transaction=transaction, version=VERSION)
def bootstrap_database():
"""
Checks if the database is empty and populates it with test data if it is.
"""
print("Checking database for existing data...")
conn = get_db_connection()
if conn is None:
print("Database connection failed. Exiting.")
exit(1)
with conn.cursor() as cur:
cur.execute("SELECT COUNT(*) FROM transactions")
count = cur.fetchone()['count']
if count == 0:
print("Database is empty. Populating with test data...")
test_data = generate_test_data.get_test_transactions()
for transaction in test_data:
cur.execute(
"""
INSERT INTO transactions (
transaction_type, company_division, address_1, address_2,
city, province, region, postal_code, is_primary, source_date, source_description,
grant_type, description, amount, recipient, commodity_class, contract_number, comments
) VALUES (
%(transaction_type)s, %(company_division)s, %(address_1)s, %(address_2)s,
%(city)s, %(province)s, %(region)s, %(postal_code)s, %(is_primary)s, %(source_date)s,
%(source_description)s, %(grant_type)s, %(description)s, %(amount)s, %(recipient)s,
%(commodity_class)s, %(contract_number)s, %(comments)s
)
""",
transaction
)
conn.commit()
print("Successfully inserted 10 test transactions.")
conn.close()
if __name__ == '__main__':
port = int(os.environ.get('FLASK_RUN_PORT', 5001))
app.run(host='0.0.0.0', port=port)

View File

@ -0,0 +1,373 @@
{
"transactions": [
{
"transaction_id": 1,
"transaction_no": "78708",
"transaction_type": "Subcontract",
"company_division": "C A E Inc",
"address_1": "5585 Cote de Liesse",
"address_2": "P O Box 1800",
"city": "ST LAURENT",
"province": "QC",
"region": "Quebec",
"postal_code": "H4T 1G6",
"is_primary": true,
"source_date": "2023-08-23",
"source_description": "Source Description",
"grant_type": "Grant Type",
"description": "7000XR Full Flight Simulator (FFS) in Global 6000/6500 configuration (subc)",
"amount": 0.0,
"recipient": "US Army",
"commodity_class": "Aerospace",
"contract_number": "SUMMARY",
"comments": "Subcontract with Leidos, US, through CAE Defense & Security. In support of the High Accuracy Detection and Exploitation System (HADES) program.",
"created_at": "2025-07-02T19:30:43.640251"
},
{
"transaction_id": 2,
"transaction_no": "2021-11783",
"transaction_type": "Invoice",
"company_division": "L3Harris Technologies Communication Systems",
"address_1": "100 N Riverside Plaza",
"address_2": "",
"city": "Chicago",
"province": "IL",
"region": "Illinois",
"postal_code": "60606",
"is_primary": false,
"source_date": "2025-02-06",
"source_description": "Source from Sole Source",
"grant_type": "",
"description": "Battlefield management software suite",
"amount": 98787185.46,
"recipient": "US Navy",
"commodity_class": "Surveillance",
"contract_number": "CONT-9738-C",
"comments": "Includes technology transfer and local production",
"created_at": "2025-07-02T19:31:13.337006"
},
{
"transaction_id": 3,
"transaction_no": "2020-28186",
"transaction_type": "Purchase Order",
"company_division": "Elbit Systems Land Systems",
"address_1": "100 N Riverside Plaza",
"address_2": "",
"city": "Chicago",
"province": "IL",
"region": "Illinois",
"postal_code": "60606",
"is_primary": true,
"source_date": "2024-08-04",
"source_description": "Source from Direct Award",
"grant_type": "Type IV",
"description": "Satellite communications terminals",
"amount": 40307275.77,
"recipient": "Australian Defence Force",
"commodity_class": "Logistics",
"contract_number": "SUMMARY",
"comments": "Urgent operational requirement for deployed forces",
"created_at": "2025-07-02T19:31:13.339737"
},
{
"transaction_id": 4,
"transaction_no": "2024-46146",
"transaction_type": "Purchase Order",
"company_division": "Thales Group Defense & Security",
"address_1": "100 N Riverside Plaza",
"address_2": "",
"city": "Chicago",
"province": "IL",
"region": "Illinois",
"postal_code": "60606",
"is_primary": true,
"source_date": "2024-09-30",
"source_description": "Source from Direct Award",
"grant_type": "Type II",
"description": "Naval vessel propulsion components",
"amount": 42033336.08,
"recipient": "UK Ministry of Defence",
"commodity_class": "Cybersecurity",
"contract_number": "CONT-4826-D",
"comments": "Replaces aging legacy systems currently in service",
"created_at": "2025-07-02T19:31:13.342318"
},
{
"transaction_id": 5,
"transaction_no": "2020-45049",
"transaction_type": "Purchase Order",
"company_division": "Northrop Grumman Mission Systems",
"address_1": "1025 W NASA Boulevard",
"address_2": "",
"city": "Melbourne",
"province": "FL",
"region": "Florida",
"postal_code": "32919",
"is_primary": false,
"source_date": "2024-11-27",
"source_description": "Source from Framework Agreement",
"grant_type": "",
"description": "Aegis Combat System software updates",
"amount": 64502031.29,
"recipient": "UK Ministry of Defence",
"commodity_class": "Protective Equipment",
"contract_number": "SUMMARY",
"comments": "Subcontract with Leidos, US, through CAE Defense & Security. In support of the High Accuracy Detection and Exploitation System (HADES) program.",
"created_at": "2025-07-02T19:31:13.344968"
},
{
"transaction_id": 6,
"transaction_no": "2021-99601",
"transaction_type": "Subcontract",
"company_division": "MBDA Missile Systems",
"address_1": "870 Winter Street",
"address_2": "",
"city": "Waltham",
"province": "MA",
"region": "Massachusetts",
"postal_code": "02451",
"is_primary": false,
"source_date": "2022-10-08",
"source_description": "Source from Competitive Bid",
"grant_type": "Type I",
"description": "Long-range precision fires development",
"amount": 10778323.11,
"recipient": "US Marine Corps",
"commodity_class": "Software",
"contract_number": "CONT-8144-A",
"comments": "Replaces aging legacy systems currently in service",
"created_at": "2025-07-02T19:31:13.347491"
},
{
"transaction_id": 7,
"transaction_no": "2021-99541",
"transaction_type": "Subcontract",
"company_division": "Northrop Grumman Mission Systems",
"address_1": "1101 Wilson Boulevard",
"address_2": "Suite 2000",
"city": "Arlington",
"province": "VA",
"region": "Virginia",
"postal_code": "22209",
"is_primary": false,
"source_date": "2023-02-22",
"source_description": "Source from Direct Award",
"grant_type": "Type III",
"description": "THAAD missile defense interceptors",
"amount": 53622255.95,
"recipient": "German Armed Forces",
"commodity_class": "Logistics",
"contract_number": "SUMMARY",
"comments": "Joint development program with international partners",
"created_at": "2025-07-02T19:31:13.351123"
},
{
"transaction_id": 8,
"transaction_no": "2021-42294",
"transaction_type": "Subcontract",
"company_division": "Thales Group Defense & Security",
"address_1": "100 N Riverside Plaza",
"address_2": "",
"city": "Chicago",
"province": "IL",
"region": "Illinois",
"postal_code": "60606",
"is_primary": false,
"source_date": "2023-06-13",
"source_description": "Source from Sole Source",
"grant_type": "Type II",
"description": "THAAD missile defense interceptors",
"amount": 67639798.11,
"recipient": "Australian Defence Force",
"commodity_class": "Electronics",
"contract_number": "SUMMARY",
"comments": "Replaces aging legacy systems currently in service",
"created_at": "2025-07-02T19:31:13.353980"
},
{
"transaction_id": 9,
"transaction_no": "2023-67502",
"transaction_type": "Subcontract",
"company_division": "BAE Systems Electronic Systems",
"address_1": "5585 Cote de Liesse",
"address_2": "P O Box 1800",
"city": "ST LAURENT",
"province": "QC",
"region": "Quebec",
"postal_code": "H4T 1G6",
"is_primary": false,
"source_date": "2023-09-07",
"source_description": "Source from Framework Agreement",
"grant_type": "Type III",
"description": "Command and control software development",
"amount": 59006974.62,
"recipient": "Australian Defence Force",
"commodity_class": "Logistics",
"contract_number": "CONT-8954-C",
"comments": "Follows successful completion of prototype testing phase",
"created_at": "2025-07-02T19:31:13.355840"
},
{
"transaction_id": 10,
"transaction_no": "2025-37332",
"transaction_type": "Contract",
"company_division": "Leonardo S.p.A. Helicopters",
"address_1": "Tour Carpe Diem",
"address_2": "31 Place des Corolles",
"city": "Courbevoie",
"province": "",
"region": "\u00cele-de-France",
"postal_code": "92400",
"is_primary": true,
"source_date": "2023-09-22",
"source_description": "Source from Framework Agreement",
"grant_type": "Type I",
"description": "F-35 Lightning II Joint Strike Fighter components",
"amount": 99293527.27,
"recipient": "US Space Force",
"commodity_class": "Electronics",
"contract_number": "SUMMARY",
"comments": "Follows successful completion of prototype testing phase",
"created_at": "2025-07-02T19:31:13.358113"
},
{
"transaction_id": 11,
"transaction_no": "2025-97753",
"transaction_type": "Memorandum of Understanding",
"company_division": "Saab AB Aeronautics",
"address_1": "870 Winter Street",
"address_2": "",
"city": "Waltham",
"province": "MA",
"region": "Massachusetts",
"postal_code": "02451",
"is_primary": false,
"source_date": "2023-12-31",
"source_description": "Source from Framework Agreement",
"grant_type": "Type II",
"description": "F-35 Lightning II Joint Strike Fighter components",
"amount": 80186777.36,
"recipient": "German Armed Forces",
"commodity_class": "Aerospace",
"contract_number": "SUMMARY",
"comments": "Follows successful completion of prototype testing phase",
"created_at": "2025-07-02T19:31:13.360869"
},
{
"transaction_id": 12,
"transaction_no": "2020-21874",
"transaction_type": "Purchase Order",
"company_division": "Thales Group Defense & Security",
"address_1": "Ottobrunn",
"address_2": "Willy-Messerschmitt-Str. 1",
"city": "Munich",
"province": "",
"region": "Bavaria",
"postal_code": "85521",
"is_primary": false,
"source_date": "2022-10-22",
"source_description": "Source from Framework Agreement",
"grant_type": "Type III",
"description": "THAAD missile defense interceptors",
"amount": 56116899.6,
"recipient": "French Armed Forces",
"commodity_class": "Defense",
"contract_number": "CONT-6498-B",
"comments": "Joint development program with international partners",
"created_at": "2025-07-02T19:31:13.363525"
},
{
"transaction_id": 13,
"transaction_no": "2021-77008",
"transaction_type": "Purchase Order",
"company_division": "CAE Inc Defense & Security",
"address_1": "5585 Cote de Liesse",
"address_2": "P O Box 1800",
"city": "ST LAURENT",
"province": "QC",
"region": "Quebec",
"postal_code": "H4T 1G6",
"is_primary": true,
"source_date": "2024-04-20",
"source_description": "Source from Direct Award",
"grant_type": "",
"description": "Ballistic missile early warning radar",
"amount": 68699920.25,
"recipient": "Israeli Defense Forces",
"commodity_class": "Naval Systems",
"contract_number": "CONT-5837-D",
"comments": "Includes technology transfer and local production",
"created_at": "2025-07-02T19:31:13.366312"
},
{
"transaction_id": 14,
"transaction_no": "2025-18493",
"transaction_type": "Grant",
"company_division": "BAE Systems Electronic Systems",
"address_1": "1025 W NASA Boulevard",
"address_2": "",
"city": "Melbourne",
"province": "FL",
"region": "Florida",
"postal_code": "32919",
"is_primary": true,
"source_date": "2023-05-31",
"source_description": "Source from RFP",
"grant_type": "Type I",
"description": "Aegis Combat System software updates",
"amount": 37067541.29,
"recipient": "NATO",
"commodity_class": "Naval Systems",
"contract_number": "SUMMARY",
"comments": "Part of larger modernization initiative",
"created_at": "2025-07-02T19:31:13.370023"
},
{
"transaction_id": 15,
"transaction_no": "2022-45237",
"transaction_type": "Grant",
"company_division": "Northrop Grumman Mission Systems",
"address_1": "5585 Cote de Liesse",
"address_2": "P O Box 1800",
"city": "ST LAURENT",
"province": "QC",
"region": "Quebec",
"postal_code": "H4T 1G6",
"is_primary": true,
"source_date": "2025-01-02",
"source_description": "Source from Direct Award",
"grant_type": "Type II",
"description": "Counter-UAS detection and defeat systems",
"amount": 17804484.99,
"recipient": "German Armed Forces",
"commodity_class": "Medical",
"contract_number": "CONT-3919-A",
"comments": "Follows successful completion of prototype testing phase",
"created_at": "2025-07-02T19:31:13.373169"
},
{
"transaction_id": 16,
"transaction_no": "2022-68024",
"transaction_type": "Memorandum of Understanding",
"company_division": "Rheinmetall Vehicle Systems",
"address_1": "5585 Cote de Liesse",
"address_2": "P O Box 1800",
"city": "ST LAURENT",
"province": "QC",
"region": "Quebec",
"postal_code": "H4T 1G6",
"is_primary": true,
"source_date": "2025-04-29",
"source_description": "Source from Sole Source",
"grant_type": "",
"description": "Tactical radio communication systems",
"amount": 56484890.19,
"recipient": "US Navy",
"commodity_class": "Munitions",
"contract_number": "SUMMARY",
"comments": "Joint development program with international partners",
"created_at": "2025-07-02T19:31:13.376183"
}
],
"documents": []
}

View File

@ -0,0 +1,60 @@
#!/usr/bin/env python3
import requests
import json
import random
from faker import Faker
fake = Faker()
# Configuration
BASE_URL = "http://localhost:5000"
API_URL = f"{BASE_URL}/api/transaction"
NUM_TRANSACTIONS = 10
# Sample data choices
TRANSACTION_TYPES = ['Subcontract', 'Purchase Order', 'Invoice', 'Contract']
COMPANY_DIVISIONS = ['Defense Systems', 'Aerospace Products', 'Technology Services', 'Corporate']
COMMODITY_CLASSES = ['Aerospace', 'Defense', 'Electronics', 'Software', 'Services']
def generate_transaction():
"""Generates a single fake transaction."""
return {
"transaction_type": random.choice(TRANSACTION_TYPES),
"company_division": random.choice(COMPANY_DIVISIONS),
"address_1": fake.street_address(),
"address_2": fake.secondary_address(),
"city": fake.city(),
"province": fake.state_abbr(),
"region": fake.state(),
"postal_code": fake.zipcode(),
"is_primary": fake.boolean(),
"source_date": fake.date_between(start_date='-2y', end_date='today').isoformat(),
"source_description": fake.sentence(nb_words=10),
"grant_type": fake.word().capitalize(),
"description": fake.paragraph(nb_sentences=3),
"amount": round(random.uniform(1000.0, 500000.0), 2),
"recipient": fake.company(),
"commodity_class": random.choice(COMMODITY_CLASSES),
"contract_number": fake.bothify(text='??-####-####'),
"comments": fake.sentence()
}
def get_test_transactions(count=10):
"""Generates a list of fake transactions."""
return [generate_transaction() for _ in range(count)]
if __name__ == '__main__':
headers = {'Content-Type': 'application/json'}
for _ in range(NUM_TRANSACTIONS):
transaction_data = generate_transaction()
try:
response = requests.post(API_URL, headers=headers, data=json.dumps(transaction_data))
if response.status_code == 201:
print(f"Successfully created transaction: {response.json().get('id')}")
else:
print(f"Failed to create transaction. Status code: {response.status_code}, Response: {response.text}")
except requests.exceptions.ConnectionError as e:
print(f"Connection to {API_URL} failed. Make sure the Flask application is running.")
break
print(json.dumps(get_test_transactions(), indent=4))

View File

@ -0,0 +1,44 @@
import psycopg2
import os
def init_db():
# Database connection parameters
conn = psycopg2.connect(
host="192.168.1.119",
port=5433,
dbname="testdb",
user="testuser",
password="testpass"
)
conn.autocommit = True
cursor = conn.cursor()
# Read schema file
with open('schema.sql', 'r') as f:
sql_script = f.read()
# Execute schema
cursor.execute(sql_script)
# Insert sample data
cursor.execute('''
INSERT INTO transactions (
transaction_type, company_division, address_1, address_2,
city, province, region, postal_code, is_primary, source_date, source_description,
grant_type, description, amount, recipient, commodity_class, contract_number, comments
) VALUES (
'Subcontract', 'C A E Inc', '5585 Cote de Liesse', 'P O Box 1800',
'ST LAURENT', 'QC', 'Quebec', 'H4T 1G6', true, '2023-08-23', 'Source Description',
'Grant Type', '7000XR Full Flight Simulator (FFS) in Global 6000/6500 configuration (subc)',
0.00, 'US Army', 'Aerospace', 'SUMMARY',
'Subcontract with Leidos, US, through CAE Defense & Security. In support of the High Accuracy Detection and Exploitation System (HADES) program.'
)
''')
cursor.close()
conn.close()
print("Database initialized successfully with sample data.")
if __name__ == "__main__":
init_db()

View File

@ -0,0 +1,10 @@
Flask==2.2.2
psycopg2-binary==2.9.3
requests==2.28.1
Faker==15.3.3
gunicorn==20.1.0
Werkzeug==2.3.7
Jinja2==3.1.2
MarkupSafe==2.1.3
itsdangerous==2.1.2
click==8.1.7

View File

@ -0,0 +1,45 @@
-- Drop tables if they exist
DROP TABLE IF EXISTS transaction_documents;
DROP TABLE IF EXISTS transactions;
-- Create transactions table
CREATE TABLE transactions (
id SERIAL PRIMARY KEY,
transaction_no SERIAL UNIQUE,
transaction_type VARCHAR(50),
company_division VARCHAR(100),
address_1 VARCHAR(255),
address_2 VARCHAR(255),
city VARCHAR(100),
province VARCHAR(100),
region VARCHAR(100),
postal_code VARCHAR(20),
is_primary BOOLEAN,
source_date DATE,
source_description TEXT,
grant_type VARCHAR(100),
description TEXT,
amount NUMERIC(15, 2),
recipient VARCHAR(255),
commodity_class VARCHAR(100),
contract_number VARCHAR(100),
comments TEXT,
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);
-- Create table for document attachments
CREATE TABLE transaction_documents (
document_id SERIAL PRIMARY KEY,
transaction_id INTEGER REFERENCES transactions(id) ON DELETE CASCADE,
filename VARCHAR(255) NOT NULL,
file_path VARCHAR(500) NOT NULL,
document_type VARCHAR(100),
description TEXT,
note TEXT,
upload_date TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);
-- Create indexes for better performance
CREATE INDEX idx_transaction_no ON transactions(transaction_no);
CREATE INDEX idx_transaction_date ON transactions(source_date);
CREATE INDEX idx_transaction_documents ON transaction_documents(transaction_id);

View File

@ -0,0 +1,183 @@
{% extends "base.html" %}
{% block title %}API Documentation - Project Ploughshares{% endblock %}
{% block content %}
<div class="row">
<div class="col-md-12">
<h2>Project Ploughshares API Documentation</h2>
<p class="lead">Base URL: <code>http://{{ server_name }}</code></p>
<div class="card mb-4">
<div class="card-header">
<h3>Endpoints</h3>
</div>
<div class="card-body">
<h4 class="mt-3">1. List All Transactions</h4>
<div class="bg-light p-3 mb-3">
<p><strong>GET</strong> <code>/api/transactions</code></p>
<h5>Complete Example:</h5>
<pre class="bg-dark text-light p-2"><code id="getAllTransactions">curl -X GET "http://{{ server_name }}/api/transactions"</code></pre>
<button class="btn btn-sm btn-secondary" onclick="copyToClipboard('getAllTransactions')">Copy</button>
<h5>Response:</h5>
<pre class="bg-dark text-light p-2"><code>[
{
"transaction_id": 1,
"transaction_no": "78708",
"transaction_type": "Subcontract",
"company_division": "C A E Inc",
"amount": 0.00,
"recipient": "US Army",
"created_at": "2023-07-02T12:34:56.789012"
},
{
"transaction_id": 2,
"transaction_no": "78709",
"transaction_type": "Purchase Order",
"company_division": "Example Corp",
"amount": 1000.00,
"recipient": "Test Recipient",
"created_at": "2023-07-03T10:11:12.131415"
}
]</code></pre>
</div>
<h4 class="mt-4">2. Get Transaction Details</h4>
<div class="bg-light p-3 mb-3">
<p><strong>GET</strong> <code>/api/transaction/{id}</code></p>
<h5>Complete Example:</h5>
<pre class="bg-dark text-light p-2"><code id="getTransaction">curl -X GET "http://{{ server_name }}/api/transaction/1"</code></pre>
<button class="btn btn-sm btn-secondary" onclick="copyToClipboard('getTransaction')">Copy</button>
<h5>Response:</h5>
<pre class="bg-dark text-light p-2"><code>{
"transaction": {
"transaction_id": 1,
"transaction_no": "78708",
"transaction_type": "Subcontract",
"company_division": "C A E Inc",
"address_1": "5585 Cote de Liesse",
"address_2": "P O Box 1800",
"city": "ST LAURENT",
"province": "QC",
"region": "Quebec",
"postal_code": "H4T 1G6",
"is_primary": true,
"source_date": "2023-08-23",
"source_description": "Source Description",
"description": "7000XR Full Flight Simulator (FFS) in Global 6000/6500 configuration (subc)",
"amount": 0.00,
"recipient": "US Army",
"commodity_class": "Aerospace",
"contract_number": "SUMMARY",
"comments": "Subcontract with Leidos, US, through CAE Defense & Security...",
"created_at": "2023-07-02T12:34:56.789012"
},
"documents": [
{
"document_id": 1,
"transaction_id": 1,
"filename": "78708_20240501.pdf",
"file_path": "1/78708_20240501.pdf",
"document_type": "Contract",
"description": "Contract document",
"note": "Original contract",
"upload_date": "2023-07-02T12:34:56.789012"
}
]
}</code></pre>
</div>
<h4 class="mt-4">3. Create New Transaction</h4>
<div class="bg-light p-3 mb-3">
<p><strong>POST</strong> <code>/api/transaction</code></p>
<h5>Complete Example:</h5>
<pre class="bg-dark text-light p-2"><code id="createTransaction">curl -X POST "http://{{ server_name }}/api/transaction" \
-H "Content-Type: application/json" \
-d '{
"transaction_no": "12345",
"transaction_type": "Purchase Order",
"company_division": "Example Corp",
"description": "Test transaction",
"amount": 1000.00,
"recipient": "Test Recipient"
}'</code></pre>
<button class="btn btn-sm btn-secondary" onclick="copyToClipboard('createTransaction')">Copy</button>
<h5>Response:</h5>
<pre class="bg-dark text-light p-2"><code>{
"message": "Transaction created successfully",
"transaction_id": 2
}</code></pre>
</div>
<h4 class="mt-4">4. Update Transaction</h4>
<div class="bg-light p-3 mb-3">
<p><strong>PUT</strong> <code>/api/transaction/{id}</code></p>
<h5>Complete Example:</h5>
<pre class="bg-dark text-light p-2"><code id="updateTransaction">curl -X PUT "http://{{ server_name }}/api/transaction/2" \
-H "Content-Type: application/json" \
-d '{
"transaction_type": "Purchase Order",
"company_division": "Updated Corp",
"description": "Updated transaction",
"amount": 1500.00,
"recipient": "Updated Recipient"
}'</code></pre>
<button class="btn btn-sm btn-secondary" onclick="copyToClipboard('updateTransaction')">Copy</button>
<h5>Response:</h5>
<pre class="bg-dark text-light p-2"><code>{
"message": "Transaction updated successfully"
}</code></pre>
</div>
<h4 class="mt-4">5. Delete Transaction</h4>
<div class="bg-light p-3 mb-3">
<p><strong>DELETE</strong> <code>/api/transaction/{id}</code></p>
<h5>Complete Example:</h5>
<pre class="bg-dark text-light p-2"><code id="deleteTransaction">curl -X DELETE "http://{{ server_name }}/api/transaction/3"</code></pre>
<button class="btn btn-sm btn-secondary" onclick="copyToClipboard('deleteTransaction')">Copy</button>
<h5>Response:</h5>
<pre class="bg-dark text-light p-2"><code>{
"message": "Transaction deleted successfully"
}</code></pre>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block scripts %}
<script>
function copyToClipboard(elementId) {
const element = document.getElementById(elementId);
const text = element.textContent;
navigator.clipboard.writeText(text).then(
function() {
// Show a temporary success message instead of an alert
const button = document.querySelector(`button[onclick="copyToClipboard('${elementId}')"]`);
const originalText = button.textContent;
button.textContent = "Copied!";
button.classList.remove("btn-secondary");
button.classList.add("btn-success");
setTimeout(() => {
button.textContent = originalText;
button.classList.remove("btn-success");
button.classList.add("btn-secondary");
}, 1500);
}
);
}
</script>
{% endblock %}

View File

@ -0,0 +1,109 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}Project Ploughshares - Transaction Management System{% endblock %}</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.0/font/bootstrap-icons.css">
<style>
body {
padding-top: 20px;
padding-bottom: 20px;
}
.header {
border-bottom: 1px solid #e5e5e5;
margin-bottom: 30px;
}
.header h3 {
margin-top: 0;
margin-bottom: 0;
line-height: 40px;
}
.footer {
padding-top: 19px;
color: #777;
border-top: 1px solid #e5e5e5;
margin-top: 30px;
}
.form-group {
margin-bottom: 15px;
}
.document-card {
margin-bottom: 15px;
}
.logo {
max-height: 40px;
margin-right: 10px;
}
.navbar-brand {
display: flex;
align-items: center;
}
.currency-value {
font-weight: 600;
color: #28a745;
}
.amount-cell {
text-align: right;
}
td:has(.currency-value) {
text-align: right;
}
.version {
font-size: 0.8em;
color: #999;
}
</style>
</head>
<body>
<div class="container">
<div class="header clearfix">
<nav class="navbar navbar-expand-lg navbar-light bg-light">
<div class="container-fluid">
<a class="navbar-brand" href="{{ url_for('index') }}">
<span class="fw-bold text-primary">Project Ploughshares</span> - Transaction Management
</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav">
<li class="nav-item">
<a class="nav-link" href="{{ url_for('index') }}">Home</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{{ url_for('create_transaction') }}">New Transaction</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{{ url_for('api_docs') }}">API Documentation</a>
</li>
</ul>
</div>
</div>
</nav>
</div>
{% with messages = get_flashed_messages() %}
{% if messages %}
<div class="alert alert-info" role="alert">
{% for message in messages %}
{{ message }}
{% endfor %}
</div>
{% endif %}
{% endwith %}
<div class="content">
{% block content %}{% endblock %}
</div>
<footer class="footer">
<p>&copy; 2023 Project Ploughshares - Transaction Management System <span class="version">v{{ version }}</span></p>
</footer>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
{% block scripts %}{% endblock %}
</body>
</html>

View File

@ -0,0 +1,83 @@
{% extends "base.html" %}
{% block title %}Transactions - Project Ploughshares{% endblock %}
{% block content %}
<div class="container-fluid mt-4">
<div class="card">
<div class="card-header d-flex justify-content-between align-items-center">
<h2>Transactions</h2>
<a href="{{ url_for('create_transaction') }}" class="btn btn-success">
<i class="bi bi-plus-lg"></i> New Transaction
</a>
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-hover">
<thead class="thead-light">
<tr>
<th>Transaction No.</th>
<th>Type</th>
<th>Division</th>
<th class="amount-cell">Amount</th>
<th>Source Date</th>
<th>Recipient</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{% for transaction in transactions %}
<tr>
<td>{{ transaction['transaction_no'] }}</td>
<td>{{ transaction['transaction_type'] }}</td>
<td>{{ transaction['company_division'] }}</td>
<td class="amount-cell"><span class="currency-value">{{ transaction['amount']|currency }}</span></td>
<td>{{ transaction['source_date'].strftime('%Y-%m-%d') if transaction['source_date'] else 'N/A' }}</td>
<td>{{ transaction['recipient'] }}</td>
<td>
<a href="{{ url_for('view_transaction', id=transaction['id']) }}" class="btn btn-sm btn-info">
<i class="bi bi-eye"></i>
</a>
<a href="{{ url_for('update_transaction', id=transaction['id']) }}" class="btn btn-sm btn-warning">
<i class="bi bi-pencil"></i>
</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
</div>
{% endblock %}
{% block scripts %}
<script>
document.addEventListener('DOMContentLoaded', function() {
const searchInput = document.getElementById('searchInput');
const searchButton = document.getElementById('searchButton');
const tableRows = document.querySelectorAll('tbody tr');
function filterTable() {
const searchTerm = searchInput.value.toLowerCase();
tableRows.forEach(row => {
const text = row.textContent.toLowerCase();
if (text.includes(searchTerm)) {
row.style.display = '';
} else {
row.style.display = 'none';
}
});
}
searchButton.addEventListener('click', filterTable);
searchInput.addEventListener('keyup', function(e) {
if (e.key === 'Enter') {
filterTable();
}
});
});
</script>
{% endblock %}

View File

@ -0,0 +1,102 @@
{% extends "base.html" %}
{% block title %}{% if transaction %}Edit{% else %}New{% endif %} Transaction - Project Ploughshares{% endblock %}
{% block content %}
<div class="container mt-5">
<div class="card">
<div class="card-header">
<h3>{% if transaction %}Edit{% else %}New{% endif %} Transaction</h3>
</div>
<div class="card-body">
<form action="{{ url_for('create_transaction') if not transaction else url_for('update_transaction', id=transaction.id) }}" method="post" class="needs-validation" novalidate>
<div class="row">
<div class="col-md-6">
<div class="form-group">
<label for="transaction_type">Transaction Type</label>
<input type="text" class="form-control" id="transaction_type" name="transaction_type" value="{{ transaction.transaction_type if transaction else '' }}" required>
<div class="invalid-feedback">Please enter a transaction type.</div>
</div>
<div class="form-group">
<label for="company_division">Company Division</label>
<input type="text" class="form-control" id="company_division" name="company_division" value="{{ transaction.company_division if transaction else '' }}" required>
<div class="invalid-feedback">Please enter a company division.</div>
</div>
<div class="form-group">
<label for="address_1">Address 1</label>
<input type="text" class="form-control" id="address_1" name="address_1" value="{{ transaction.address_1 if transaction else '' }}">
</div>
<div class="form-group">
<label for="address_2">Address 2</label>
<input type="text" class="form-control" id="address_2" name="address_2" value="{{ transaction.address_2 if transaction else '' }}">
</div>
<div class="form-group">
<label for="city">City</label>
<input type="text" class="form-control" id="city" name="city" value="{{ transaction.city if transaction else '' }}">
</div>
<div class="form-group">
<label for="province">Province</label>
<input type="text" class="form-control" id="province" name="province" value="{{ transaction.province if transaction else '' }}">
</div>
<div class="form-group">
<label for="region">Region</label>
<input type="text" class="form-control" id="region" name="region" value="{{ transaction.region if transaction else '' }}">
</div>
<div class="form-group">
<label for="postal_code">Postal Code</label>
<input type="text" class="form-control" id="postal_code" name="postal_code" value="{{ transaction.postal_code if transaction else '' }}">
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="is_primary" name="is_primary" {% if transaction and transaction.is_primary %}checked{% endif %}>
<label class="form-check-label" for="is_primary">Is Primary</label>
</div>
</div>
<div class="col-md-6">
<div class="form-group">
<label for="source_date">Source Date</label>
<input type="date" class="form-control" id="source_date" name="source_date" value="{{ transaction.source_date.strftime('%Y-%m-%d') if transaction and transaction.source_date else '' }}">
</div>
<div class="form-group">
<label for="source_description">Source Description</label>
<textarea class="form-control" id="source_description" name="source_description" rows="3">{{ transaction.source_description if transaction else '' }}</textarea>
</div>
<div class="form-group">
<label for="grant_type">Grant Type</label>
<input type="text" class="form-control" id="grant_type" name="grant_type" value="{{ transaction.grant_type if transaction else '' }}">
</div>
<div class="form-group">
<label for="description">Description</label>
<textarea class="form-control" id="description" name="description" rows="3">{{ transaction.description if transaction else '' }}</textarea>
</div>
<div class="form-group">
<label for="amount">Amount</label>
<div class="input-group">
<span class="input-group-text">$</span>
<input type="number" step="0.01" min="0" class="form-control" id="amount" name="amount" value="{{ transaction.amount if transaction else '' }}" placeholder="0.00">
</div>
</div>
<div class="form-group">
<label for="recipient">Recipient</label>
<input type="text" class="form-control" id="recipient" name="recipient" value="{{ transaction.recipient if transaction else '' }}">
</div>
<div class="form-group">
<label for="commodity_class">Commodity Class</label>
<input type="text" class="form-control" id="commodity_class" name="commodity_class" value="{{ transaction.commodity_class if transaction else '' }}">
</div>
<div class="form-group">
<label for="contract_number">Contract Number</label>
<input type="text" class="form-control" id="contract_number" name="contract_number" value="{{ transaction.contract_number if transaction else '' }}">
</div>
<div class="form-group">
<label for="comments">Comments</label>
<textarea class="form-control" id="comments" name="comments" rows="3">{{ transaction.comments if transaction else '' }}</textarea>
</div>
</div>
</div>
<button type="submit" class="btn btn-primary mt-3">Submit</button>
<a href="{{ url_for('index') }}" class="btn btn-secondary mt-3">Cancel</a>
</form>
</div>
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,95 @@
{% extends "base.html" %}
{% block title %}Transaction {{ transaction.transaction_no }} - Project Ploughshares{% endblock %}
{% block content %}
<div class="container-fluid mt-4">
<div class="card">
<div class="card-header d-flex justify-content-between align-items-center">
<h2>Transaction #{{ transaction.transaction_no }} - Project Ploughshares</h2>
<div>
<a href="{{ url_for('update_transaction', id=transaction.id) }}" class="btn btn-warning">
<i class="bi bi-pencil"></i> Edit
</a>
<a href="{{ url_for('index') }}" class="btn btn-secondary">
<i class="bi bi-arrow-left"></i> Back to List
</a>
</div>
</div>
<div class="card-body">
<ul class="nav nav-tabs" id="myTab" role="tablist">
<li class="nav-item" role="presentation">
<button class="nav-link active" id="details-tab" data-bs-toggle="tab" data-bs-target="#details" type="button" role="tab" aria-controls="details" aria-selected="true">Details</button>
</li>
</ul>
<div class="tab-content" id="myTabContent">
<div class="tab-pane fade show active" id="details" role="tabpanel" aria-labelledby="details-tab">
<div class="p-3">
<table class="table table-bordered">
<tbody>
<tr>
<th style="width: 20%;">Transaction Type:</th>
<td>{{ transaction.transaction_type }}</td>
</tr>
<tr>
<th>Company/Division:</th>
<td>{{ transaction.company_division }}</td>
</tr>
<tr>
<th>Address:</th>
<td>
{{ transaction.address_1 }}<br>
{% if transaction.address_2 %}{{ transaction.address_2 }}<br>{% endif %}
{{ transaction.city }}, {{ transaction.province }}, {{ transaction.postal_code }}<br>
{{ transaction.region }}
</td>
</tr>
<tr>
<th>Primary:</th>
<td>{{ 'Yes' if transaction.is_primary else 'No' }}</td>
</tr>
<tr>
<th>Source Date:</th>
<td>{{ transaction.source_date.strftime('%Y-%m-%d') if transaction.source_date else 'N/A' }}</td>
</tr>
<tr>
<th>Source Description:</th>
<td>{{ transaction.source_description }}</td>
</tr>
<tr>
<th>Grant Type:</th>
<td>{{ transaction.grant_type }}</td>
</tr>
<tr>
<th>Description:</th>
<td>{{ transaction.description }}</td>
</tr>
<tr>
<th>Amount:</th>
<td class="amount-cell"><span class="currency-value">{{ transaction.amount|currency }}</span></td>
</tr>
<tr>
<th>Recipient:</th>
<td>{{ transaction.recipient }}</td>
</tr>
<tr>
<th>Commodity Class:</th>
<td>{{ transaction.commodity_class }}</td>
</tr>
<tr>
<th>Contract Number:</th>
<td>{{ transaction.contract_number }}</td>
</tr>
<tr>
<th>Comments:</th>
<td>{{ transaction.comments }}</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}

55
versionbump.sh Executable file
View File

@ -0,0 +1,55 @@
#!/bin/bash
# versionbump.sh - Script to bump version numbers in the VERSION file
# Usage: ./versionbump.sh [major|minor|patch]
set -e
VERSION_FILE="VERSION"
# Check if VERSION file exists
if [ ! -f "$VERSION_FILE" ]; then
echo "Error: $VERSION_FILE does not exist."
exit 1
fi
# Read current version
CURRENT_VERSION=$(cat "$VERSION_FILE")
echo "Current version: $CURRENT_VERSION"
# Split version into components
IFS='.' read -r MAJOR MINOR PATCH <<< "$CURRENT_VERSION"
# Check which part to bump
case "$1" in
major)
MAJOR=$((MAJOR + 1))
MINOR=0
PATCH=0
;;
minor)
MINOR=$((MINOR + 1))
PATCH=0
;;
patch)
PATCH=$((PATCH + 1))
;;
*)
echo "Usage: $0 [major|minor|patch]"
echo "Example: $0 minor"
exit 1
;;
esac
# Create new version string
NEW_VERSION="${MAJOR}.${MINOR}.${PATCH}"
echo "New version: $NEW_VERSION"
# Update VERSION file
echo "$NEW_VERSION" > "$VERSION_FILE"
echo "Updated $VERSION_FILE to $NEW_VERSION"
# Optional: Update other files that might need version updates
# Add commands here to update version in other files if needed
echo "Version bump complete!"