Switch to angular

This commit is contained in:
pratik
2025-10-14 23:32:38 -05:00
parent 1ce27976a9
commit 82e8cea256
72 changed files with 20815 additions and 991 deletions

117
app/api/tags.py Normal file
View File

@@ -0,0 +1,117 @@
from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy.orm import Session
from typing import List
from app.database import get_db
from app.models.tag import Tag
from app.schemas.tag import TagCreate, TagUpdate, TagResponse
router = APIRouter(prefix="/api/v1/tags", tags=["tags"])
@router.post("/", response_model=TagResponse, status_code=201)
async def create_tag(tag: TagCreate, db: Session = Depends(get_db)):
"""
Create a new tag
- **name**: Tag name (unique, required)
- **description**: Tag description (optional)
- **color**: Hex color code (optional, e.g., #FF5733)
"""
# Check if tag already exists
existing_tag = db.query(Tag).filter(Tag.name == tag.name).first()
if existing_tag:
raise HTTPException(status_code=400, detail=f"Tag with name '{tag.name}' already exists")
db_tag = Tag(**tag.model_dump())
db.add(db_tag)
db.commit()
db.refresh(db_tag)
return db_tag
@router.get("/", response_model=List[TagResponse])
async def list_tags(
limit: int = Query(default=100, le=1000),
offset: int = Query(default=0, ge=0),
db: Session = Depends(get_db)
):
"""List all tags with pagination"""
tags = db.query(Tag).order_by(Tag.name).offset(offset).limit(limit).all()
return tags
@router.get("/{tag_id}", response_model=TagResponse)
async def get_tag(tag_id: int, db: Session = Depends(get_db)):
"""Get tag by ID"""
tag = db.query(Tag).filter(Tag.id == tag_id).first()
if not tag:
raise HTTPException(status_code=404, detail="Tag not found")
return tag
@router.get("/name/{tag_name}", response_model=TagResponse)
async def get_tag_by_name(tag_name: str, db: Session = Depends(get_db)):
"""Get tag by name"""
tag = db.query(Tag).filter(Tag.name == tag_name).first()
if not tag:
raise HTTPException(status_code=404, detail="Tag not found")
return tag
@router.put("/{tag_id}", response_model=TagResponse)
async def update_tag(tag_id: int, tag_update: TagUpdate, db: Session = Depends(get_db)):
"""
Update a tag
- **name**: Tag name (optional)
- **description**: Tag description (optional)
- **color**: Hex color code (optional)
"""
tag = db.query(Tag).filter(Tag.id == tag_id).first()
if not tag:
raise HTTPException(status_code=404, detail="Tag not found")
# Check if new name conflicts with existing tag
if tag_update.name and tag_update.name != tag.name:
existing_tag = db.query(Tag).filter(Tag.name == tag_update.name).first()
if existing_tag:
raise HTTPException(status_code=400, detail=f"Tag with name '{tag_update.name}' already exists")
# Update fields
update_data = tag_update.model_dump(exclude_unset=True)
for field, value in update_data.items():
setattr(tag, field, value)
db.commit()
db.refresh(tag)
return tag
@router.delete("/{tag_id}")
async def delete_tag(tag_id: int, db: Session = Depends(get_db)):
"""Delete a tag"""
tag = db.query(Tag).filter(Tag.id == tag_id).first()
if not tag:
raise HTTPException(status_code=404, detail="Tag not found")
db.delete(tag)
db.commit()
return {"message": f"Tag '{tag.name}' deleted successfully"}
@router.post("/search", response_model=List[TagResponse])
async def search_tags(
query: str = Query(..., min_length=1, description="Search query"),
limit: int = Query(default=100, le=1000),
db: Session = Depends(get_db)
):
"""Search tags by name or description"""
tags = db.query(Tag).filter(
(Tag.name.ilike(f"%{query}%")) | (Tag.description.ilike(f"%{query}%"))
).order_by(Tag.name).limit(limit).all()
return tags

View File

@@ -1,7 +1,8 @@
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from app.config import settings
from app.models.artifact import Base
from app.models.artifact import Base as ArtifactBase
from app.models.tag import Base as TagBase
engine = create_engine(settings.database_url)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
@@ -9,7 +10,8 @@ SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
def init_db():
"""Initialize database tables"""
Base.metadata.create_all(bind=engine)
ArtifactBase.metadata.create_all(bind=engine)
TagBase.metadata.create_all(bind=engine)
def get_db():

View File

@@ -1,9 +1,8 @@
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from fastapi.staticfiles import StaticFiles
from fastapi.responses import FileResponse
from app.api.artifacts import router as artifacts_router
from app.api.seed import router as seed_router
from app.api.tags import router as tags_router
from app.database import init_db
from app.config import settings
import logging
@@ -38,11 +37,9 @@ app.add_middleware(
# Include routers
app.include_router(artifacts_router)
app.include_router(seed_router)
app.include_router(tags_router)
# Mount static files
static_dir = os.path.join(os.path.dirname(os.path.dirname(__file__)), "static")
if os.path.exists(static_dir):
app.mount("/static", StaticFiles(directory=static_dir), name="static")
# Note: Frontend is now served separately as an Angular application
@app.on_event("startup")
@@ -69,19 +66,15 @@ async def api_root():
@app.get("/")
async def ui_root():
"""Serve the UI"""
index_path = os.path.join(static_dir, "index.html")
if os.path.exists(index_path):
return FileResponse(index_path)
else:
return {
"message": "Test Artifact Data Lake API",
"version": "1.0.0",
"docs": "/docs",
"ui": "UI not found. Serving API only.",
"deployment_mode": settings.deployment_mode,
"storage_backend": settings.storage_backend
}
"""API root - Frontend is served separately"""
return {
"message": "Test Artifact Data Lake API",
"version": "1.0.0",
"docs": "/docs",
"frontend": "Frontend is served separately on port 4200 (development) or via reverse proxy (production)",
"deployment_mode": settings.deployment_mode,
"storage_backend": settings.storage_backend
}
@app.get("/health")

21
app/models/tag.py Normal file
View File

@@ -0,0 +1,21 @@
from sqlalchemy import Column, String, Integer, DateTime, Text
from sqlalchemy.ext.declarative import declarative_base
from datetime import datetime
Base = declarative_base()
class Tag(Base):
__tablename__ = "tags"
id = Column(Integer, primary_key=True, index=True)
name = Column(String(100), unique=True, nullable=False, index=True)
description = Column(Text)
color = Column(String(7)) # Hex color code, e.g., #FF5733
# Timestamps
created_at = Column(DateTime, default=datetime.utcnow, index=True)
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
def __repr__(self):
return f"<Tag(id={self.id}, name='{self.name}')>"

28
app/schemas/tag.py Normal file
View File

@@ -0,0 +1,28 @@
from pydantic import BaseModel, Field
from datetime import datetime
from typing import Optional
class TagBase(BaseModel):
name: str = Field(..., min_length=1, max_length=100, description="Tag name")
description: Optional[str] = Field(None, description="Tag description")
color: Optional[str] = Field(None, pattern="^#[0-9A-Fa-f]{6}$", description="Hex color code (e.g., #FF5733)")
class TagCreate(TagBase):
pass
class TagUpdate(BaseModel):
name: Optional[str] = Field(None, min_length=1, max_length=100, description="Tag name")
description: Optional[str] = Field(None, description="Tag description")
color: Optional[str] = Field(None, pattern="^#[0-9A-Fa-f]{6}$", description="Hex color code")
class TagResponse(TagBase):
id: int
created_at: datetime
updated_at: datetime
class Config:
from_attributes = True