https and ssl integration on the backend, frontend and started integration of logistics app as a separate frontend

This commit is contained in:
GotthardG
2024-11-19 09:56:05 +01:00
parent a91d74b718
commit a931bfb8ec
14 changed files with 264 additions and 267 deletions

View File

View File

@ -1 +1,2 @@
from .data import contacts, return_addresses, dewars, proposals, shipments, pucks, samples, dewar_types, serial_numbers
from .slots_data import slots

View File

@ -1,7 +1,8 @@
from app.models import ContactPerson, Address, Dewar, Proposal, Shipment, Puck, Sample, DewarType, DewarSerialNumber
from app.models import ContactPerson, Address, Dewar, Proposal, Shipment, Puck, Sample, DewarType, DewarSerialNumber, Slot
from datetime import datetime
import random
import uuid
import time
import hashlib
dewar_types = [
@ -44,8 +45,14 @@ return_addresses = [
]
# Utilize a function to generate unique IDs
def generate_unique_id():
return str(uuid.uuid4())
def generate_unique_id(length=16):
base_string = f"{time.time()}{random.randint(0, 10 ** 6)}"
hash_object = hashlib.sha256(base_string.encode())
hash_digest = hash_object.hexdigest()
short_unique_id = ''.join(random.choices(hash_digest, k=length))
return short_unique_id
# Define dewars with unique IDs
dewars = [
@ -54,20 +61,20 @@ dewars = [
dewar_serial_number_id=2, tracking_number='TRACK123',
return_address_id=1, contact_person_id=1, status='Ready for Shipping',
ready_date=datetime.strptime('2023-09-30', '%Y-%m-%d'), shipping_date=None, arrival_date=None,
returning_date=None, qrcode=generate_unique_id()
returning_date=None, unique_id=generate_unique_id()
),
Dewar(
id=2, dewar_name='Dewar Two', dewar_type_id=3,
dewar_serial_number_id=1, tracking_number='TRACK124',
return_address_id=2, contact_person_id=2, status='In Preparation',
ready_date=None, shipping_date=None, arrival_date=None, returning_date=None, qrcode=generate_unique_id()
ready_date=None, shipping_date=None, arrival_date=None, returning_date=None, unique_id=generate_unique_id()
),
Dewar(
id=3, dewar_name='Dewar Three', dewar_type_id=2,
dewar_serial_number_id=3, tracking_number='TRACK125',
return_address_id=1, contact_person_id=3, status='Not Shipped',
ready_date=datetime.strptime('2024-01-01', '%Y-%m-%d'), shipping_date=None, arrival_date=None,
returning_date=None, qrcode=''
returning_date=None, unique_id=None
),
Dewar(
id=4, dewar_name='Dewar Four', dewar_type_id=2,
@ -75,7 +82,7 @@ dewars = [
return_address_id=1, contact_person_id=3, status='Delayed',
ready_date=datetime.strptime('2024-01-01', '%Y-%m-%d'),
shipping_date=datetime.strptime('2024-01-02', '%Y-%m-%d'),
arrival_date=None, returning_date=None, qrcode=''
arrival_date=None, returning_date=None, unique_id=None
),
Dewar(
id=5, dewar_name='Dewar Five', dewar_type_id=1,
@ -83,7 +90,7 @@ dewars = [
return_address_id=1, contact_person_id=3, status='Returned',
arrival_date=datetime.strptime('2024-01-03', '%Y-%m-%d'),
returning_date=datetime.strptime('2024-01-07', '%Y-%m-%d'),
qrcode=''
unique_id=None
),
]

View File

@ -0,0 +1,36 @@
from datetime import datetime, timedelta
from app.models import Slot
slotQRCodes = [
"A1-X06SA", "A2-X06SA", "A3-X06SA", "A4-X06SA", "A5-X06SA",
"B1-X06SA", "B2-X06SA", "B3-X06SA", "B4-X06SA", "B5-X06SA",
"C1-X06SA", "C2-X06SA", "C3-X06SA", "C4-X06SA", "C5-X06SA",
"D1-X06SA", "D2-X06SA", "D3-X06SA", "D4-X06SA", "D5-X06SA",
"A1-X10SA", "A2-X10SA", "A3-X10SA", "A4-X10SA", "A5-X10SA",
"B1-X10SA", "B2-X10SA", "B3-X10SA", "B4-X10SA", "B5-X10SA",
"C1-X10SA", "C2-X10SA", "C3-X10SA", "C4-X10SA", "C5-X10SA",
"D1-X10SA", "D2-X10SA", "D3-X10SA", "D4-X10SA", "D5-X10SA",
"NB1", "NB2", "NB3", "NB4", "NB5", "NB6",
"X10SA-beamline", "X06SA-beamline", "X06DA-beamline",
"X10SA-outgoing", "X06-outgoing"
]
def timedelta_to_str(td: timedelta) -> str:
days, seconds = td.days, td.seconds
hours = days * 24 + seconds // 3600
minutes = (seconds % 3600) // 60
return f'PT{hours}H{minutes}M'
slots = [
Slot(
id=str(i + 1), # Convert id to string to match your schema
qr_code=qrcode,
label=qrcode.split('-')[0],
qr_base=qrcode.split('-')[1] if '-' in qrcode else '',
occupied=False,
needs_refill=False,
last_refill=datetime.utcnow(),
time_until_refill=timedelta_to_str(timedelta(hours=24)) # Serialize timedelta to ISO 8601 string
)
for i, qrcode in enumerate(slotQRCodes)
]

View File

@ -1,7 +1,9 @@
# database.py
from sqlalchemy.orm import Session
from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
from app import models
SQLALCHEMY_DATABASE_URL = "sqlite:///./test.db" # Use appropriate path or database URL
@ -10,7 +12,6 @@ SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Base = declarative_base()
# Dependency
def get_db():
db = SessionLocal()
@ -19,22 +20,18 @@ def get_db():
finally:
db.close()
def init_db():
# Import models inside function to avoid circular dependency
from app import models
Base.metadata.create_all(bind=engine)
def load_sample_data(session: Session):
# Import models inside function to avoid circular dependency
from app.data import contacts, return_addresses, dewars, proposals, shipments, pucks, samples, dewar_types, serial_numbers
from app import models
from app.data import contacts, return_addresses, dewars, proposals, shipments, pucks, samples, dewar_types, serial_numbers, slots
# If any data already exists, skip seeding
if session.query(models.ContactPerson).first():
return
session.add_all(contacts + return_addresses + dewars + proposals + shipments + pucks + samples + dewar_types + serial_numbers)
session.commit()
session.add_all(contacts + return_addresses + dewars + proposals + shipments + pucks + samples + dewar_types + serial_numbers + slots)
session.commit()

View File

@ -73,13 +73,13 @@ class Dewar(Base):
shipping_date = Column(Date, nullable=True)
arrival_date = Column(Date, nullable=True)
returning_date = Column(Date, nullable=True)
unique_id = Column(String(36), default=lambda: str(uuid.uuid4()), unique=True, index=True, nullable=True)
qrcode = Column(String, nullable=True)
unique_id = Column(String, unique=True, index=True, nullable=True)
shipment_id = Column(Integer, ForeignKey("shipments.id"))
return_address_id = Column(Integer, ForeignKey("addresses.id"))
contact_person_id = Column(Integer, ForeignKey("contact_persons.id"))
shipment = relationship("Shipment", back_populates="dewars")
events = relationship("LogisticsEvent", back_populates="dewar")
return_address = relationship("Address")
contact_person = relationship("ContactPerson")
pucks = relationship("Puck", back_populates="dewar")
@ -132,22 +132,30 @@ class Sample(Base):
puck_id = Column(Integer, ForeignKey('pucks.id'))
puck = relationship("Puck", back_populates="samples")
class Slot(Base):
__tablename__ = "slots"
id = Column(String, primary_key=True, index=True)
qr_code = Column(String, unique=True, index=True)
label = Column(String)
qr_base = Column(String, nullable=True)
occupied = Column(Boolean, default=False)
needs_refill = Column(Boolean, default=False)
time_until_refill = Column(Interval, nullable=True)
last_refill = Column(DateTime, default=datetime.utcnow)
time_until_refill = Column(Integer) # store as total seconds
@property
def calculate_time_until_refill(self):
if self.last_refill and self.time_until_refill:
return self.last_refill + self.time_until_refill - datetime.utcnow()
return None
class LogisticsEvent(Base):
__tablename__ = "logistics_events"
__tablename__ = 'logistics_events'
id = Column(Integer, primary_key=True, index=True, autoincrement=True)
dewar_id = Column(Integer, ForeignKey('dewars.id'), nullable=False)
slot_id = Column(String, ForeignKey('slots.id'), nullable=True)
event_type = Column(String, nullable=False)
timestamp = Column(DateTime, default=datetime.utcnow)
dewar = relationship("Dewar")
slot = relationship("Slot")
dewar = relationship("Dewar", back_populates="events")

View File

@ -1,6 +1,4 @@
import os
import tempfile # <-- Add this import
import xml.etree.ElementTree as ET
import os, tempfile, time, random, hashlib
from fastapi import APIRouter, HTTPException, status, Depends, Response
from sqlalchemy.orm import Session, joinedload
from typing import List
@ -38,9 +36,12 @@ from app.crud import get_shipments, get_shipment_by_id # Import CRUD functions
router = APIRouter()
def generate_unique_id(db: Session) -> str:
def generate_unique_id(db: Session, length: int = 16) -> str:
while True:
unique_id = str(uuid.uuid4())
base_string = f"{time.time()}{random.randint(0, 10 ** 6)}"
hash_object = hashlib.sha256(base_string.encode())
hash_digest = hash_object.hexdigest()
unique_id = ''.join(random.choices(hash_digest, k=length))
existing_dewar = db.query(DewarModel).filter(DewarModel.unique_id == unique_id).first()
if not existing_dewar:
break

View File

@ -1,107 +1,113 @@
from fastapi import APIRouter, HTTPException, Depends
from sqlalchemy.orm import Session
import logging
from datetime import datetime, timedelta
from typing import List
from app.models import Dewar as DewarModel, Slot as SlotModel, LogisticsEvent as LogisticsEventModel
from app.schemas import LogisticsEventCreate, SlotCreate, Slot as SlotSchema, Dewar as DewarSchema
from app.schemas import LogisticsEventCreate, Slot as SlotSchema, Dewar as DewarSchema
from app.database import get_db
from app.data import slots_data
import logging
router = APIRouter()
# Configure logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
@router.get("/dewars", response_model=List[DewarSchema])
async def get_all_prouts(db: Session = Depends(get_db)):
try:
dewars = db.query(DewarModel).all()
logging.info(f"Retrieved {len(dewars)} dewars from the database")
return dewars
except Exception as e:
logger.error(f"An error occurred: {e}")
raise HTTPException(status_code=500, detail="Internal server error")
async def get_all_dewars(db: Session = Depends(get_db)):
dewars = db.query(DewarModel).all()
return dewars
@router.get("/dewar/{qr_code}", response_model=DewarSchema)
async def get_dewar_by_qr_code(qr_code: str, db: Session = Depends(get_db)):
logger.info(f"Received qr_code: {qr_code}")
trimmed_qr_code = qr_code.strip()
logger.info(f"Trimmed qr_code after stripping: {trimmed_qr_code}")
@router.get("/dewar/{unique_id}", response_model=DewarSchema)
async def get_dewar_by_unique_id(unique_id: str, db: Session = Depends(get_db)):
dewar = db.query(DewarModel).filter(DewarModel.unique_id == unique_id.strip()).first()
if not dewar:
raise HTTPException(status_code=404, detail="Dewar not found")
return dewar
try:
dewar = db.query(DewarModel).filter(DewarModel.unique_id == trimmed_qr_code).first()
logger.info(f"Query Result: {dewar}")
if dewar:
return dewar
else:
logger.error(f"Dewar not found for unique_id: {trimmed_qr_code}")
raise HTTPException(status_code=404, detail="Dewar not found")
except Exception as e:
logger.error(f"An error occurred: {e}")
raise HTTPException(status_code=500, detail="Internal server error")
@router.post("/dewar/scan", response_model=LogisticsEventCreate)
@router.post("/dewar/scan", response_model=dict)
async def scan_dewar(event_data: LogisticsEventCreate, db: Session = Depends(get_db)):
dewar_qr_code = event_data.dewar_qr_code
location_qr_code = event_data.location_qr_code
transaction_type = event_data.transaction_type
try:
dewar = db.query(DewarModel).filter(DewarModel.unique_id == dewar_qr_code).first()
if not dewar:
raise HTTPException(status_code=404, detail="Dewar not found")
dewar = db.query(DewarModel).filter(DewarModel.unique_id == dewar_qr_code).first()
if not dewar:
raise HTTPException(status_code=404, detail="Dewar not found")
if transaction_type == 'incoming':
slot = db.query(SlotModel).filter(SlotModel.id == location_qr_code).first()
if not slot or slot.occupied:
raise HTTPException(status_code=404, detail="Slot not found or already occupied")
slot.occupied = True
log_event(db, dewar.id, slot.id, 'incoming')
slot = db.query(SlotModel).filter(SlotModel.qr_code == location_qr_code).first()
if transaction_type == 'incoming':
if not slot or slot.occupied:
raise HTTPException(status_code=400, detail="Slot not found or already occupied")
slot.occupied = True
elif transaction_type == 'beamline':
log_event(db, dewar.id, None, 'beamline')
elif transaction_type == 'outgoing':
if not slot or not slot.occupied:
raise HTTPException(status_code=400, detail="Slot not found or not occupied")
slot.occupied = False
elif transaction_type == 'outgoing':
slot = db.query(SlotModel).filter(SlotModel.id == location_qr_code).first()
if not slot or not slot.occupied:
raise HTTPException(status_code=404, detail="Slot not found or not occupied")
slot.occupied = False
log_event(db, dewar.id, slot.id, 'outgoing')
# Log the event
log_event(db, dewar.id, slot.id if slot else None, transaction_type)
elif transaction_type == 'release':
slot = db.query(SlotModel).filter(SlotModel.id == location_qr_code).first()
if not slot or not slot.occupied:
raise HTTPException(status_code=404, detail="Slot not found or not occupied")
slot.occupied = False
log_event(db, dewar.id, slot.id, 'released')
db.commit()
return {"message": "Status updated successfully"}
db.commit()
logger.info(f"Status updated successfully for Dewar ID: {dewar.id}")
return {"message": "Status updated successfully"}
except Exception as e:
logger.error(f"An error occurred: {e}")
raise HTTPException(status_code=500, detail="Internal server error")
def log_event(db: Session, dewar_id: int, slot_id: int, event_type: str):
new_event = LogisticsEventModel(dewar_id=dewar_id if dewar_id else None, slot_id=slot_id, event_type=event_type)
def log_event(db: Session, dewar_id: int, slot_id: str, event_type: str):
new_event = LogisticsEventModel(dewar_id=dewar_id, slot_id=slot_id, event_type=event_type)
db.add(new_event)
db.commit()
logger.info(f"Logged event {event_type} for Dewar ID: {dewar_id}")
@router.get("/slots/refill-status", response_model=List[SlotSchema])
# Convert SQLAlchemy model to dictionary (if necessary)
def slot_to_dict(slot: SlotModel) -> dict:
return {
"id": slot.id,
"qr_code": slot.qr_code,
"label": slot.label,
"qr_base": slot.qr_base,
"occupied": slot.occupied,
"needs_refill": slot.needs_refill,
"last_refill": slot.last_refill.isoformat(),
"time_until_refill": str(slot.time_until_refill) if slot.time_until_refill else None # Ensure correct format
}
@router.get("/slots", response_model=List[dict])
def read_slots(db: Session = Depends(get_db)):
return [slot_to_dict(slot) for slot in db.query(SlotModel).all()]
@router.get("/dewars/refill-status", response_model=List[DewarSchema])
async def refill_status(db: Session = Depends(get_db)):
slots_needing_refill = db.query(SlotModel).filter(SlotModel.needs_refill == True).all()
dewars = db.query(DewarModel).all()
result = []
current_time = datetime.utcnow()
for slot in slots_needing_refill:
time_until_next_refill = slot.last_refill + timedelta(hours=24) - current_time
result.append({
'slot_id': slot.id,
'needs_refill': slot.needs_refill,
'time_until_refill': str(time_until_next_refill)
})
for dewar in dewars:
last_refill_event = (
db.query(LogisticsEventModel)
.filter(
LogisticsEventModel.dewar_id == dewar.id,
LogisticsEventModel.event_type == 'refill'
)
.order_by(LogisticsEventModel.timestamp.desc())
.first()
)
time_until_refill = None
if last_refill_event:
time_until_refill = last_refill_event.timestamp + timedelta(hours=24) - current_time
dewar_data = DewarSchema(
id=dewar.id,
dewar_name=dewar.dewar_name,
unique_id=dewar.unique_id,
time_until_refill=str(time_until_refill) if time_until_refill else None # Ensure correct format
)
result.append(dewar_data)
return result

View File

@ -184,7 +184,6 @@ class DewarBase(BaseModel):
shipping_date: Optional[date]
arrival_date: Optional[date]
returning_date: Optional[date]
qrcode: str
contact_person_id: Optional[int]
return_address_id: Optional[int]
pucks: List[PuckCreate] = []
@ -216,7 +215,6 @@ class DewarUpdate(BaseModel):
shipping_date: Optional[date] = None
arrival_date: Optional[date] = None
returning_date: Optional[date] = None
qrcode: Optional[str] = None
contact_person_id: Optional[int] = None
address_id: Optional[int] = None
@ -273,20 +271,20 @@ class ShipmentCreate(BaseModel):
class UpdateShipmentComments(BaseModel):
comments: str
class LogisticsEventCreate(BaseModel):
dewar_qr_code: str
location_qr_code: str
transaction_type: str
class SlotCreate(BaseModel):
id: int
class Slot(BaseModel):
id: str
qr_code: str
label: str
qr_base: str
occupied: bool
needs_refill: bool
last_refill: datetime
occupied: bool
class Slot(BaseModel):
slot_id: int
needs_refill: bool
time_until_refill: str
class Config: