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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@ vite.config.ts.timestamp-*
*~
.venv/
venv/
__pycache__/

# Visual Studio Code
.vscode/
Expand Down
Empty file added backend/__init__.py
Empty file.
19 changes: 19 additions & 0 deletions backend/auth.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
################################################################################
## Copyright 2025 Lawrence Livermore National Security, LLC. and Binghamton University.
## See the top-level LICENSE file for details.
##
## SPDX-License-Identifier: Apache-2.0
################################################################################

from fastapi import Header, HTTPException, status
from typing import Optional


async def get_forwarded_user(x_forwarded_user: Optional[str] = Header(None, alias="X-Forwarded-User")) -> str:
"""Extract authenticated user from X-Forwarded-User header"""
if not x_forwarded_user:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Missing X-Forwarded-User header. Authentication required.",
)
return x_forwarded_user
Empty file added backend/database/__init__.py
Empty file.
51 changes: 51 additions & 0 deletions backend/database/engine.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
################################################################################
## Copyright 2025 Lawrence Livermore National Security, LLC. and Binghamton University.
## See the top-level LICENSE file for details.
##
## SPDX-License-Identifier: Apache-2.0
##########################################################################

from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession, async_sessionmaker
from sqlalchemy.exc import OperationalError
from sqlalchemy.orm import declarative_base
import os

if "MARIADB_HOST" not in os.environ:
engine = AsyncSessionLocal = None
else:
DB_USER = os.getenv("MARIADB_USER", "user")
DB_PASSWORD = os.getenv("MARIADB_PASSWORD", "password")
DB_HOST = os.getenv("MARIADB_HOST", "localhost")
DB_PORT = os.getenv("MARIADB_PORT", "8080")
DATABASE_URL = f"mysql+aiomysql://{DB_USER}:{DB_PASSWORD}@{DB_HOST}:{DB_PORT}"

try:
engine = create_async_engine(
DATABASE_URL,
echo=True,
pool_size=10,
max_overflow=20,
pool_pre_ping=True,
pool_recycle=3600,
)

AsyncSessionLocal = async_sessionmaker(engine, class_=AsyncSession, expire_on_commit=False)
except OperationalError:
engine = AsyncSessionLocal = None

Base = declarative_base()


async def get_db():
if AsyncSessionLocal is None:
yield None
return
async with AsyncSessionLocal() as session:
try:
yield session
await session.commit()
except Exception:
await session.rollback()
raise
finally:
await session.close()
71 changes: 71 additions & 0 deletions backend/database/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
################################################################################
## Copyright 2025 Lawrence Livermore National Security, LLC. and Binghamton University.
## See the top-level LICENSE file for details.
##
## SPDX-License-Identifier: Apache-2.0
################################################################################

from sqlalchemy import Column, String, DateTime, Boolean, ForeignKey, Index, Text, JSON, Float
from sqlalchemy.orm import relationship, Mapped, mapped_column
from datetime import datetime
from typing import Optional
from backend.database.engine import Base


class Project(Base):
__tablename__ = "projects"

id: Mapped[str] = mapped_column(String(255), primary_key=True)
user: Mapped[str] = mapped_column(String(255), nullable=False, index=True)
name: Mapped[str] = mapped_column(String(255), nullable=False)
created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, nullable=False)
last_modified: Mapped[datetime] = mapped_column(
DateTime, default=datetime.utcnow, onupdate=datetime.utcnow, nullable=False
)

experiments: Mapped[list["Experiment"]] = relationship(
"Experiment", back_populates="project", cascade="all, delete-orphan"
)

__table_args__ = (Index("idx_user_last_modified", "user", "last_modified"),)


class Experiment(Base):
__tablename__ = "experiments"

id: Mapped[str] = mapped_column(String(255), primary_key=True)
project_id: Mapped[str] = mapped_column(String(255), ForeignKey("projects.id", ondelete="CASCADE"), nullable=False)
user: Mapped[str] = mapped_column(String(255), nullable=False, index=True)
name: Mapped[str] = mapped_column(String(255), nullable=False)
created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, nullable=False)
last_modified: Mapped[datetime] = mapped_column(
DateTime, default=datetime.utcnow, onupdate=datetime.utcnow, nullable=False
)
is_running: Mapped[Optional[bool]] = mapped_column(Boolean, default=False)

# System state fields
smiles: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
problem_type: Mapped[Optional[str]] = mapped_column(String(255), nullable=True)
problem_name: Mapped[Optional[str]] = mapped_column(String(255), nullable=True)
system_prompt: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
problem_prompt: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
property_type: Mapped[Optional[str]] = mapped_column(String(255), nullable=True)
custom_property_name: Mapped[Optional[str]] = mapped_column(String(255), nullable=True)
custom_property_desc: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
custom_property_ascending: Mapped[Optional[bool]] = mapped_column(Boolean, nullable=True)

# Complex JSON fields for nested data structures
tree_nodes: Mapped[Optional[str]] = mapped_column(JSON, nullable=True)
edges: Mapped[Optional[str]] = mapped_column(JSON, nullable=True)
metrics_history: Mapped[Optional[str]] = mapped_column(JSON, nullable=True)
visible_metrics: Mapped[Optional[str]] = mapped_column(JSON, nullable=True)
graph_state: Mapped[Optional[str]] = mapped_column(JSON, nullable=True)
auto_zoom: Mapped[Optional[bool]] = mapped_column(Boolean, nullable=True)
sidebar_state: Mapped[Optional[str]] = mapped_column(JSON, nullable=True)

# Experiment context
experiment_context: Mapped[Optional[str]] = mapped_column(Text, nullable=True)

project: Mapped["Project"] = relationship("Project", back_populates="experiments")

__table_args__ = (Index("idx_user_project", "user", "project_id"),)
109 changes: 109 additions & 0 deletions backend/database/schemas.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
################################################################################
## Copyright 2025 Lawrence Livermore National Security, LLC. and Binghamton University.
## See the top-level LICENSE file for details.
##
## SPDX-License-Identifier: Apache-2.0
################################################################################

from pydantic import BaseModel, Field
from datetime import datetime
from typing import List, Optional, Any


class ExperimentBase(BaseModel):
name: str


class ExperimentCreate(ExperimentBase):
pass


class ExperimentUpdate(BaseModel):
name: Optional[str] = None
is_running: Optional[bool] = None

# System state fields
smiles: Optional[str] = None
problem_type: Optional[str] = Field(None, alias="problemType")
problem_name: Optional[str] = Field(None, alias="problemName")
system_prompt: Optional[str] = Field(None, alias="systemPrompt")
problem_prompt: Optional[str] = Field(None, alias="problemPrompt")
property_type: Optional[str] = Field(None, alias="propertyType")
custom_property_name: Optional[str] = Field(None, alias="customPropertyName")
custom_property_desc: Optional[str] = Field(None, alias="customPropertyDesc")
custom_property_ascending: Optional[bool] = Field(None, alias="customPropertyAscending")

# Complex nested data (stored as JSON)
tree_nodes: Optional[Any] = Field(None, alias="treeNodes")
edges: Optional[Any] = None
metrics_history: Optional[Any] = Field(None, alias="metricsHistory")
visible_metrics: Optional[Any] = Field(None, alias="visibleMetrics")
graph_state: Optional[Any] = Field(None, alias="graphState")
auto_zoom: Optional[bool] = Field(None, alias="autoZoom")
sidebar_state: Optional[Any] = Field(None, alias="sidebarState")

# Experiment context
experiment_context: Optional[str] = Field(None, alias="experimentContext")

class Config:
populate_by_name = True


class Experiment(ExperimentBase):
id: str
project_id: str = Field(..., alias="projectId")
user: str
created_at: datetime = Field(..., alias="createdAt")
last_modified: datetime = Field(..., alias="lastModified")
is_running: Optional[bool] = Field(None, alias="isRunning")

# System state fields
smiles: Optional[str] = None
problem_type: Optional[str] = Field(None, alias="problemType")
problem_name: Optional[str] = Field(None, alias="problemName")
system_prompt: Optional[str] = Field(None, alias="systemPrompt")
problem_prompt: Optional[str] = Field(None, alias="problemPrompt")
property_type: Optional[str] = Field(None, alias="propertyType")
custom_property_name: Optional[str] = Field(None, alias="customPropertyName")
custom_property_desc: Optional[str] = Field(None, alias="customPropertyDesc")
custom_property_ascending: Optional[bool] = Field(None, alias="customPropertyAscending")

# Complex nested data
tree_nodes: Optional[Any] = Field(None, alias="treeNodes")
edges: Optional[Any] = None
metrics_history: Optional[Any] = Field(None, alias="metricsHistory")
visible_metrics: Optional[Any] = Field(None, alias="visibleMetrics")
graph_state: Optional[Any] = Field(None, alias="graphState")
auto_zoom: Optional[bool] = Field(None, alias="autoZoom")
sidebar_state: Optional[Any] = Field(None, alias="sidebarState")

# Experiment context
experiment_context: Optional[str] = Field(None, alias="experimentContext")

class Config:
from_attributes = True
populate_by_name = True


class ProjectBase(BaseModel):
name: str


class ProjectCreate(ProjectBase):
pass


class ProjectUpdate(ProjectBase):
pass


class Project(ProjectBase):
id: str
user: str
created_at: datetime = Field(..., alias="createdAt")
last_modified: datetime = Field(..., alias="lastModified")
experiments: List[Experiment] = []

class Config:
from_attributes = True
populate_by_name = True
Empty file added backend/routers/__init__.py
Empty file.
Loading