Index: .gitignore
===================================================================
--- .gitignore	(revision f41a3f0b4f9e00e589236ea75a868b2e4ef2286b)
+++ .gitignore	(revision 9a5d0df2843f362d0864f4f08452e5594bdc194b)
@@ -26,2 +26,8 @@
 .flutter-plugins
 .flutter-plugins-dependencies
+
+
+# Avoid committing project-specific files:
+__pycache__/
+pgdata/
+venv/
Index: Dockerfile
===================================================================
--- Dockerfile	(revision 9a5d0df2843f362d0864f4f08452e5594bdc194b)
+++ Dockerfile	(revision 9a5d0df2843f362d0864f4f08452e5594bdc194b)
@@ -0,0 +1,24 @@
+FROM python:3.10-slim
+
+# Install system dependencies
+RUN apt-get update && apt-get install -y \
+    gcc \
+    build-essential \
+    libpq-dev \
+    postgresql-client \
+    --no-install-recommends && \
+    apt-get clean && \
+    rm -rf /var/lib/apt/lists/*
+
+WORKDIR /app
+
+# Install dependencies
+COPY requirements.txt .
+RUN pip install --upgrade pip && pip install --no-cache-dir -r requirements.txt
+
+# Copy project files
+COPY . .
+
+# Expose port and run the application
+EXPOSE 8000
+CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000", "--reload"]
Index: app/auth.py
===================================================================
--- app/auth.py	(revision 9a5d0df2843f362d0864f4f08452e5594bdc194b)
+++ app/auth.py	(revision 9a5d0df2843f362d0864f4f08452e5594bdc194b)
@@ -0,0 +1,41 @@
+import jwt
+from datetime import datetime, timedelta
+from fastapi import HTTPException, status
+from passlib.hash import bcrypt
+from dotenv import load_dotenv
+import os
+
+load_dotenv()
+
+def hash_password(password: str) -> str:
+    return bcrypt.hash(password)
+
+def verify_password(password: str, hashed_password: str) -> bool:
+    return bcrypt.verify(password, hashed_password)
+
+def is_admin(email: str) -> bool:
+    return email in {os.getenv('AUTH_ADMIN_EMAILS')}
+
+def create_access_token(data: dict):
+    to_encode = data.copy()
+    expire = datetime.utcnow() + timedelta(minutes= {os.getenv('AUTH_ACCESS_TOKEN_EXPIRE_MINUTES')})
+    to_encode.update({"exp": expire})
+    encoded_jwt = jwt.encode(to_encode, {os.getenv('AUTH_SECRET_KEY')}, algorithm={os.getenv('AUTH_ALGORITHM')})
+    return encoded_jwt
+
+def decode_access_token(token: str):
+    try:
+        payload = jwt.decode(token, {os.getenv('AUTH_SECRET_KEY')}, algorithms=[{os.getenv('AUTH_ALGORITHM')}])
+        return payload
+    except jwt.ExpiredSignatureError:
+        raise HTTPException(
+            status_code=status.HTTP_401_UNAUTHORIZED,
+            detail="Token has expired",
+            headers={"WWW-Authenticate": "Bearer"},
+        )
+    except jwt.PyJWTError:
+        raise HTTPException(
+            status_code=status.HTTP_401_UNAUTHORIZED,
+            detail="Invalid token",
+            headers={"WWW-Authenticate": "Bearer"},
+        )
Index: app/database.py
===================================================================
--- app/database.py	(revision 9a5d0df2843f362d0864f4f08452e5594bdc194b)
+++ app/database.py	(revision 9a5d0df2843f362d0864f4f08452e5594bdc194b)
@@ -0,0 +1,29 @@
+from sqlalchemy import create_engine
+from sqlalchemy.ext.declarative import declarative_base
+from sqlalchemy.orm import sessionmaker
+
+# Database URL
+DATABASE_URL = "postgresql://vasilaki:{os.getenv('DB_PASSWORD')}@db:5432/fein1"
+
+# Create the database engine
+engine = create_engine(DATABASE_URL, pool_pre_ping=True)
+
+# Create a configured "Session" class
+SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
+
+# Base class for models
+Base = declarative_base()
+
+try:
+    with engine.connect() as connection:
+        print("Database connection successful")
+except Exception as e:
+    print(f"Failed to connect to the database: {e}")
+
+# Dependency to get the database session
+def get_db():
+    db = SessionLocal()
+    try:
+        yield db
+    finally:
+        db.close()
Index: app/main.py
===================================================================
--- app/main.py	(revision 9a5d0df2843f362d0864f4f08452e5594bdc194b)
+++ app/main.py	(revision 9a5d0df2843f362d0864f4f08452e5594bdc194b)
@@ -0,0 +1,246 @@
+from fastapi import FastAPI, Depends, HTTPException
+from sqlalchemy.orm import Session
+from app.database import get_db
+from app.models import User, TransactionAccount, Transaction, TransactionBreakdown, Tag, TagAssignedToTransaction
+from pydantic import BaseModel
+from typing import List, Optional
+from datetime import datetime
+from fastapi.security import OAuth2PasswordBearer
+from app.auth import create_access_token, decode_access_token, is_admin, hash_password, verify_password
+
+# Initialize FastAPI app
+app = FastAPI()
+
+oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/auth/login/")
+
+# Pydantic schemas for validation and response models
+class UserCreate(BaseModel):
+    user_name: str
+    email: str
+    password: str
+
+class UserResponse(BaseModel):
+    user_id: int
+    user_name: str
+    email: str
+
+    class Config:
+        from_attributes = True
+
+class TransactionAccountCreate(BaseModel):
+    account_name: str
+    balance: float
+
+class TransactionAccountResponse(BaseModel):
+    transaction_account_id: int
+    account_name: str
+    balance: float
+
+    class Config:
+        from_attributes = True
+
+class TransactionCreate(BaseModel):
+    transaction_name: str
+    amount: float
+    net_amount: float
+    date: datetime  # Use this format for date strings "2024-12-21 12:00:00+02:00"
+
+class TransactionUpdate(BaseModel):
+    transaction_name: str = None
+    amount: float = None
+    net_amount: float = None
+    date: datetime = None
+
+class TransactionResponse(BaseModel):
+    transaction_id: int
+    transaction_name: str
+    amount: float
+    net_amount: float
+    date: datetime
+
+    class Config:
+        json_encoders = {
+            datetime: lambda v: v.isoformat()  # Serialize datetime as ISO 8601 string
+        }
+        from_attributes = True
+
+class TagCreate(BaseModel):
+    tag_name: str
+
+class TagResponse(BaseModel):
+    tag_id: int
+    tag_name: str
+
+    class Config:
+        from_attributes = True
+
+class TagAssign(BaseModel):
+    transaction_id: int
+    tag_id: int
+
+class AuthRequest(BaseModel):
+    user_name: Optional[str] = None
+    email: str
+    password: str
+
+class AuthResponse(BaseModel):
+    access_token: str
+    token_type: str
+
+# Dependency to get the current user
+def get_current_user(token: str = Depends(oauth2_scheme), db: Session = Depends(get_db)):
+    payload = decode_access_token(token)
+    user_id = payload.get("sub")
+    if user_id is None:
+        raise HTTPException(status_code=401, detail="Invalid authentication credentials")
+
+    user = db.query(User).filter(User.user_id == user_id).first()
+    if not user:
+        raise HTTPException(status_code=401, detail="Invalid authentication credentials")
+
+    return user
+
+# Routes
+@app.get("/")
+def read_root():
+    return {"message": "Welcome to the Fein Prototype API"}
+
+@app.post("/auth/register/", response_model=AuthResponse)
+def register(auth_request: AuthRequest, db: Session = Depends(get_db)):
+    # Check if email already exists
+    existing_user = db.query(User).filter(User.email == auth_request.email).first()
+    if existing_user:
+        raise HTTPException(status_code=400, detail="Email already registered")
+
+    # Hash password and create new user
+    hashed_password = hash_password(auth_request.password)
+    new_user = User(
+        user_name=auth_request.user_name,
+        email=auth_request.email,
+        password=hashed_password
+    )
+    db.add(new_user)
+    db.commit()
+    db.refresh(new_user)
+
+    # Return access token
+    access_token = create_access_token({"sub": new_user.user_id})
+    return {"access_token": access_token, "token_type": "bearer"}
+
+@app.post("/auth/login/", response_model=AuthResponse)
+def login(auth_request: AuthRequest, db: Session = Depends(get_db)):
+    # Verify email and password
+    user = db.query(User).filter(User.email == auth_request.email).first()
+    if not user or not verify_password(auth_request.password, user.password):
+        raise HTTPException(status_code=401, detail="Invalid email or password")
+
+    # Return access token
+    access_token = create_access_token({"sub": user.user_id})
+    return {"access_token": access_token, "token_type": "bearer"}
+
+@app.get("/admin/accounts/", response_model=List[TransactionAccountResponse])
+def admin_get_all_accounts(user: User = Depends(get_current_user), db: Session = Depends(get_db)):
+    if not is_admin(user.email):
+        raise HTTPException(status_code=403, detail="Access denied")
+    return db.query(TransactionAccount).all()
+
+
+@app.post("/accounts/", response_model=TransactionAccountResponse)
+def create_account(account: TransactionAccountCreate, db: Session = Depends(get_db)):
+    new_account = TransactionAccount(account_name=account.account_name, balance=account.balance)
+    db.add(new_account)
+    db.commit()
+    db.refresh(new_account)
+    return new_account
+
+@app.get("/accounts/", response_model=List[TransactionAccountResponse])
+def get_accounts(user: User = Depends(get_current_user), db: Session = Depends(get_db)):
+    return db.query(TransactionAccount).filter(TransactionAccount.user_id == user.user_id).all()
+
+@app.post("/transactions/", response_model=TransactionResponse)
+def create_transaction(transaction: TransactionCreate, db: Session = Depends(get_db)):
+    new_transaction = Transaction(
+        transaction_name=transaction.transaction_name,
+        amount=transaction.amount,
+        net_amount=transaction.net_amount,
+        date=transaction.date
+    )
+    db.add(new_transaction)
+    db.commit()
+    db.refresh(new_transaction)
+    return new_transaction
+
+@app.get("/transactions/", response_model=List[TransactionResponse])
+def get_transactions(db: Session = Depends(get_db)):
+    transactions = db.query(Transaction).all()
+    
+    # Convert datetime fields to strings manually
+    return [
+        {
+            **transaction.__dict__,
+            "date": transaction.date.isoformat()  # Convert datetime to ISO 8601 string
+        }
+        for transaction in transactions
+    ]
+
+@app.put("/transactions/{transaction_id}", response_model=TransactionResponse)
+def update_transaction(transaction_id: int, transaction_update: TransactionUpdate, db: Session = Depends(get_db)):
+    transaction = db.query(Transaction).filter(Transaction.transaction_id == transaction_id).first()
+    if not transaction:
+        raise HTTPException(status_code=404, detail="Transaction not found")
+
+    for key, value in transaction_update.dict(exclude_unset=True).items():
+        setattr(transaction, key, value)
+
+    db.commit()
+    db.refresh(transaction)
+    return transaction
+
+@app.delete("/transactions/{transaction_id}")
+def delete_transaction(transaction_id: int, db: Session = Depends(get_db)):
+    transaction = db.query(Transaction).filter(Transaction.transaction_id == transaction_id).first()
+    if not transaction:
+        raise HTTPException(status_code=404, detail="Transaction not found")
+
+    db.delete(transaction)
+    db.commit()
+    return {"message": "Transaction deleted successfully"}
+
+@app.get("/reports/", response_model=dict)
+def get_reports(db: Session = Depends(get_db)):
+    total_spent = db.query(Transaction).with_entities(Transaction.amount).filter(Transaction.amount > 0).all()
+    return {"report": "Reports feature placeholder"}
+
+@app.post("/tags/", response_model=TagResponse)
+def create_tag(tag: TagCreate, db: Session = Depends(get_db)):
+    new_tag = Tag(tag_name=tag.tag_name)
+    db.add(new_tag)
+    db.commit()
+    db.refresh(new_tag)
+    return new_tag
+
+@app.get("/tags/", response_model=List[TagResponse])
+def get_tags(db: Session = Depends(get_db)):
+    tags = db.query(Tag).all()
+    return tags
+
+@app.post("/tags/assign/", response_model=dict)
+def assign_tag_to_transaction(tag_assign: TagAssign, db: Session = Depends(get_db)):
+    transaction = db.query(Transaction).filter(Transaction.transaction_id == tag_assign.transaction_id).first()
+    tag = db.query(Tag).filter(Tag.tag_id == tag_assign.tag_id).first()
+
+    if not transaction or not tag:
+        raise HTTPException(status_code=404, detail="Transaction or Tag not found")
+
+    assignment = TagAssignedToTransaction(transaction_id=tag_assign.transaction_id, tag_id=tag_assign.tag_id)
+    db.add(assignment)
+    db.commit()
+    return {"message": "Tag assigned to transaction successfully"}
+
+@app.get("/transactions/{transaction_id}/tags", response_model=List[TagResponse])
+def get_transaction_tags(transaction_id: int, db: Session = Depends(get_db)):
+    transaction = db.query(Transaction).filter(Transaction.transaction_id == transaction_id).first()
+    if not transaction:
+        raise HTTPException(status_code=404, detail="Transaction not found")
+
+    return transaction.tags
Index: app/models.py
===================================================================
--- app/models.py	(revision 9a5d0df2843f362d0864f4f08452e5594bdc194b)
+++ app/models.py	(revision 9a5d0df2843f362d0864f4f08452e5594bdc194b)
@@ -0,0 +1,57 @@
+from sqlalchemy import Column, Integer, String, Numeric, ForeignKey, DateTime, Boolean
+from sqlalchemy.orm import relationship
+from sqlalchemy.ext.declarative import declarative_base
+
+Base = declarative_base()
+
+# Define Models
+class User(Base):
+    __tablename__ = 'user'
+    user_id = Column(Integer, primary_key=True, autoincrement=True)
+    user_name = Column(String(50), nullable=False)
+    email = Column(String(100), unique=True, nullable=False)
+    password = Column(String(255), nullable=False)
+    accounts = relationship("TransactionAccount", back_populates="user")
+
+class TransactionAccount(Base):
+    __tablename__ = 'transaction_account'
+    transaction_account_id = Column(Integer, primary_key=True, autoincrement=True)
+    account_name = Column(String(50), nullable=False)
+    balance = Column(Numeric(10, 2), default=0, nullable=False)
+    user_id = Column(Integer, ForeignKey('user.user_id'))
+    user = relationship("User", back_populates="accounts")
+
+class Tag(Base):
+    __tablename__ = 'tag'
+    tag_id = Column(Integer, primary_key=True, autoincrement=True)
+    tag_name = Column(String(50), nullable=False)
+    transactions = relationship(
+        "Transaction", secondary="tag_assigned_to_transaction", back_populates="tags"
+    )
+
+class Transaction(Base):
+    __tablename__ = 'transaction'
+    transaction_id = Column(Integer, primary_key=True, autoincrement=True)
+    transaction_name = Column(String(100), nullable=False)
+    amount = Column(Numeric(10, 2), nullable=False)
+    net_amount = Column(Numeric(10, 2), nullable=False)
+    date = Column(DateTime, nullable=False)
+    breakdowns = relationship("TransactionBreakdown", back_populates="transaction")
+    tags = relationship(
+        "Tag", secondary="tag_assigned_to_transaction", back_populates="transactions"
+    )
+
+class TransactionBreakdown(Base):
+    __tablename__ = 'transaction_breakdown'
+    transaction_breakdown_id = Column(Integer, primary_key=True, autoincrement=True)
+    transaction_id = Column(Integer, ForeignKey('transaction.transaction_id'))
+    transaction_account_id = Column(Integer, ForeignKey('transaction_account.transaction_account_id'))
+    spent_amount = Column(Numeric(10, 2), nullable=False, default=0)
+    earned_amount = Column(Numeric(10, 2), nullable=False, default=0)
+    transaction = relationship("Transaction", back_populates="breakdowns")
+
+class TagAssignedToTransaction(Base):
+    __tablename__ = 'tag_assigned_to_transaction'
+    tag_assigned_to_transaction_id = Column(Integer, primary_key=True, autoincrement=True)
+    transaction_id = Column(Integer, ForeignKey('transaction.transaction_id'))
+    tag_id = Column(Integer, ForeignKey('tag.tag_id'))
Index: cli/cli_app.py
===================================================================
--- cli/cli_app.py	(revision 9a5d0df2843f362d0864f4f08452e5594bdc194b)
+++ cli/cli_app.py	(revision 9a5d0df2843f362d0864f4f08452e5594bdc194b)
@@ -0,0 +1,300 @@
+import requests
+from app.auth import is_admin
+
+BASE_URL = "http://localhost:8000"
+
+def main_menu():
+    print("\nMain Menu")
+    print("1. Register")
+    print("2. Login")
+    print("3. Exit")
+    choice = input("Choose an option: ")
+
+    if choice == "1":
+        register()
+    elif choice == "2":
+        token = login()
+        if token:
+            user_menu(token)
+    elif choice == "3":
+        print("Exiting...")
+    else:
+        print("Invalid choice. Please try again.")
+
+def register():
+    print("\nRegister")
+    user_name = input("Enter your username: ")
+    email = input("Enter your email: ")
+    password = input("Enter your password: ")
+
+    response = requests.post(f"{BASE_URL}/auth/register/", json={
+        "user_name": user_name,
+        "email": email,
+        "password": password
+    })
+
+    if response.status_code == 200:
+        print("Registration successful.")
+    else:
+        print("Registration failed. Please try again.")
+        print(f"Error: {response.json().get('detail', 'Unknown error')}")
+    
+def login():
+    print("\nLog in")
+    email = input("Enter your email: ")
+    password = input("Enter your password: ")
+
+    response = requests.get(f"{BASE_URL}/auth/login/", json={
+        "email": email,
+        "password": password
+    })
+
+    if response.status_code == 200:
+        data = response.json()
+        print("Log in successful.")
+        return data["access_token"]
+    else:
+        print("Invalid login credentials.")
+        return None
+
+
+def admin_menu(user_id):
+    while True:
+        print("\nAdmin Menu")
+        print("1. View All Transaction Accounts")
+        print("2. Log Out")
+        choice = input("Choose an option: ")
+
+        if choice == "1":
+            admin_view_all_accounts()
+        elif choice == "2":
+            print("Logging out...")
+            break
+        else:
+            print("Invalid choice. Please try again.")
+
+def admin_view_all_accounts():
+    print("\nAll Transaction Accounts")
+    response = requests.get(f"{BASE_URL}/admin/accounts/")
+
+    if response.status_code == 200:
+        accounts = response.json()
+        for account in accounts:
+            print(f"Account ID: {account['transaction_account_id']}, Name: {account['account_name']}, Balance: {account['balance']}")
+    else:
+        print("Failed to retrieve accounts.")
+        print(f"Error: {response.json().get('detail', 'Unknown error')}")
+
+def user_menu(token):
+    while True:
+        print("\nUser Menu")
+        print("1. Add Transaction Account")
+        print("2. View Transaction Accounts")
+        print("3. Add Transaction")
+        print("4. View Transactions")
+        print("5. Modify Transaction")
+        print("6. Delete Transaction")
+        print("7. Add Tag")
+        print("8. View Tags")
+        print("9. Assign Tag to Transaction")
+        print("10. View Transaction Tags")
+        print("11. View Reports")
+        print("12. Log Out")
+        choice = input("Choose an option: ")
+
+        if choice == "1":
+            add_transaction_account()
+        elif choice == "2":
+            view_transaction_accounts()
+        elif choice == "3":
+            add_transaction()
+        elif choice == "4":
+            view_transactions()
+        elif choice == "5":
+            modify_transaction()
+        elif choice == "6":
+            delete_transaction()
+        elif choice == "7":
+            add_tag()
+        elif choice == "8":
+            view_tags()
+        elif choice == "9":
+            assign_tag_to_transaction()
+        elif choice == "10":
+            view_transaction_tags()
+        elif choice == "11":
+            view_reports()
+        elif choice == "12":
+            print("Logging out...")
+            break
+        else:
+            print("Invalid choice. Please try again.")
+
+def add_transaction_account():
+    print("\nAdd Transaction Account")
+    account_name = input("Enter account name: ")
+    balance = float(input("Enter balance: "))
+
+    response = requests.post(f"{BASE_URL}/accounts/", json={
+        "account_name": account_name,
+        "balance": balance
+    })
+
+    if response.status_code == 200:
+        print("Transaction account added successfully.")
+    else:
+        print("Failed to add transaction account.")
+        print(f"Error: {response.json().get('detail', 'Unknown error')}")
+
+def view_transaction_accounts(user_id):
+    print("\nTransaction Accounts")
+    response = requests.get(f"{BASE_URL}/accounts/", params={"user_id": user_id})
+
+    if response.status_code == 200:
+        accounts = response.json()
+        for account in accounts:
+            print(f"Account ID: {account['transaction_account_id']}")
+            print(f"Account Name: {account['account_name']}")
+            print(f"Balance: {account['balance']}")
+            print()
+    else:
+        print("Failed to retrieve transaction accounts.")
+        print(f"Error: {response.json().get('detail', 'Unknown error')}")
+
+def add_transaction():
+    print("\nAdd Transaction")
+    transaction_name = input("Enter transaction name: ")
+    amount = float(input("Enter amount: "))
+    date = input("Enter date (YYYY-MM-DDTHH:MM:SS): ")
+
+    response = requests.get(f"{BASE_URL}/transactions/", json={
+        "transaction_name": transaction_name,
+        "amount": amount,
+        "net_amount": 0,
+        "date": date
+    })
+
+    if response.status_code == 200:
+        print("Transaction added successfully.")
+    else:
+        print("Failed to add transaction.")
+        print(f"Error: {response.json().get('detail', 'Unknown error')}")
+
+def view_transactions():
+    print("\nView Transactions")
+    response = requests.get(f"{BASE_URL}/transactions/")
+
+    if response.status_code == 200:
+        transactions = response.json()
+        for transaction in transactions:
+            print(f"Transaction ID: {transaction['transaction_id']}")
+            print(f"Transaction Name: {transaction['transaction_name']}")
+            print(f"Amount: {transaction['amount']}")
+            print(f"Net Amount: {transaction['net_amount']}")
+            print(f"Date: {transaction['date']}")
+            print()
+    else:
+        print("Failed to retrieve transactions.")
+        print(f"Error: {response.json().get('detail', 'Unknown error')}")
+
+def modify_transaction():
+    print("\nModify Transaction")
+    transaction_id = int(input("Enter transaction ID to modify: "))
+    transaction_name = input("Enter new transaction name (or leave blank): ")
+    amount = input("Enter new amount (or leave blank): ")
+    net_amount = input("Enter new net amount (or leave blank): ")
+    date = input("Enter new date (YYYY-MM-DDTHH:MM:SS) (or leave blank): ")
+
+    data = {}
+    if transaction_name:
+        data["transaction_name"] = transaction_name
+    if amount:
+        data["amount"] = float(amount)
+    if net_amount:
+        data["net_amount"] = float(net_amount)
+    if date:
+        data["date"] = date
+
+    response = requests.put(f"{BASE_URL}/transactions/{transaction_id}", json=data)
+
+    if response.status_code == 200:
+        print("Transaction modified successfully.")
+    else:
+        print("Failed to modify transaction.")
+        print(f"Error: {response.json().get('detail', 'Unknown error')}")
+
+def delete_transaction():
+    print("\nDelete Transaction")
+    transaction_id = int(input("Enter transaction ID to delete: "))
+
+    response = requests.delete(f"{BASE_URL}/transactions/{transaction_id}")
+
+    if response.status_code == 200:
+        print("Transaction deleted successfully.")
+    else:
+        print("Failed to delete transaction.")
+        print(f"Error: {response.json().get('detail', 'Unknown error')}")
+
+def add_tag():
+    print("\nAdd Tag")
+    tag_name = input("Enter tag name: ")
+
+    response = requests.post(f"{BASE_URL}/tags/", json={"tag_name": tag_name})
+
+    if response.status_code == 200:
+        print("Tag added successfully.")
+    else:
+        print("Failed to add tag.")
+        print(f"Error: {response.json().get('detail', 'Unknown error')}")
+
+def view_tags():
+    print("\nView Tags")
+    response = requests.get(f"{BASE_URL}/tags/")
+
+    if response.status_code == 200:
+        tags = response.json()
+        for tag in tags:
+            print(f"ID: {tag['tag_id']}, Name: {tag['tag_name']}")
+    else:
+        print("Error retrieving tags.")
+
+def assign_tag_to_transaction():
+    print("\nAssign Tag to Transaction")
+    transaction_id = int(input("Enter transaction ID: "))
+    tag_id = int(input("Enter tag ID: "))
+
+    response = requests.post(f"{BASE_URL}/tags/assign/", json={
+        "transaction_id": transaction_id,
+        "tag_id": tag_id
+    })
+
+    if response.status_code == 200:
+        print("Tag assigned successfully.")
+    else:
+        print("Failed to assign tag.")
+        print(f"Error: {response.json().get('detail', 'Unknown error')}")
+
+def view_transaction_tags():
+    print("\nView Transaction Tags")
+    transaction_id = int(input("Enter transaction ID to view its tags: "))
+
+    response = requests.get(f"{BASE_URL}/transactions/{transaction_id}/tags")
+
+    if response.status_code == 200:
+        tags = response.json()
+        for tag in tags:
+            print(f"ID: {tag['tag_id']}, Name: {tag['tag_name']}")
+    else:
+        print(f"Error retrieving tags for transaction. {response.json().get('detail', 'Unknown error')}")
+
+def view_reports():
+    print("\nView Reports")
+    response = requests.get(f"{BASE_URL}/reports/")
+
+    if response.status_code == 200:
+        print(response.json())
+    else:
+        print("Error retrieving reports.")
+
+if __name__ == "__main__":
+    main_menu()
Index: docker-compose.yml
===================================================================
--- docker-compose.yml	(revision 9a5d0df2843f362d0864f4f08452e5594bdc194b)
+++ docker-compose.yml	(revision 9a5d0df2843f362d0864f4f08452e5594bdc194b)
@@ -0,0 +1,31 @@
+services:
+  db:
+    image: postgres:latest
+    container_name: fein1
+    environment:
+      POSTGRES_USER: vasilaki
+      POSTGRES_PASSWORD: adminpassword
+      POSTGRES_DB: fein1
+    ports:
+      - "5432:5432"
+    networks:
+      - fein_network
+    volumes:
+      - ./pgdata:/var/lib/postgresql/data
+
+
+  app:
+    build: .
+    container_name: fein_prototype_app
+    environment:
+      DATABASE_URL: postgresql://vasilaki:adminpassword@db:5432/fein1
+    depends_on:
+      - db
+    ports:
+      - "8000:8000"
+    networks:
+      - fein_network
+
+networks:
+  fein_network:
+    driver: bridge
Index: requirements.txt
===================================================================
--- requirements.txt	(revision 9a5d0df2843f362d0864f4f08452e5594bdc194b)
+++ requirements.txt	(revision 9a5d0df2843f362d0864f4f08452e5594bdc194b)
@@ -0,0 +1,10 @@
+fastapi
+sqlalchemy
+psycopg2
+uvicorn
+pydantic
+passlib[bcrypt]
+requests
+bcrypt==4.0.1
+passlib
+pyjwt
