Phase 3.1: Add remaining local files
This commit is contained in:
commit
c4706f1779
26 files changed
+1780
-1
No files matched your search
@@ -19,6 +19,7 @@ router = APIRouter()
|
||||
|
||||
def enrich_completion_log(db: Session, log: ChoreCompletionLog) -> dict:
|
||||
"""Add related information to completion log."""
|
||||
<<<<<<< HEAD
|
||||
# Get chore info
|
||||
chore = db.query(Chore).filter(Chore.id == log.chore_id).first()
|
||||
|
||||
@@ -26,6 +27,11 @@ def enrich_completion_log(db: Session, log: ChoreCompletionLog) -> dict:
|
||||
user = db.query(User).filter(User.id == log.user_id).first()
|
||||
|
||||
# Get verified_by info if exists
|
||||
=======
|
||||
chore = db.query(Chore).filter(Chore.id == log.chore_id).first()
|
||||
user = db.query(User).filter(User.id == log.user_id).first()
|
||||
|
||||
>>>>>>> 65c71b3d67d462fe9ecc01a1c2aa17e54b626fe2
|
||||
verified_by_name = None
|
||||
if log.verified_by_user_id:
|
||||
verified_by = db.query(User).filter(User.id == log.verified_by_user_id).first()
|
||||
@@ -48,6 +54,7 @@ def enrich_completion_log(db: Session, log: ChoreCompletionLog) -> dict:
|
||||
|
||||
|
||||
@router.post("/{chore_id}/complete", response_model=log_schemas.ChoreCompletionLog, status_code=status.HTTP_201_CREATED)
|
||||
<<<<<<< HEAD
|
||||
def complete_chore(
|
||||
chore_id: int,
|
||||
notes: Optional[str] = None,
|
||||
@@ -98,17 +105,37 @@ def complete_chore(
|
||||
ChoreAssignment.chore_id == chore_id
|
||||
).all()
|
||||
|
||||
=======
|
||||
def complete_chore(chore_id: int, notes: Optional[str] = None, db: Session = Depends(get_db), current_user: User = Depends(get_current_user)):
|
||||
chore = db.query(Chore).filter(Chore.id == chore_id).first()
|
||||
if not chore:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Chore not found")
|
||||
|
||||
assignment = db.query(ChoreAssignment).filter(ChoreAssignment.chore_id == chore_id, ChoreAssignment.user_id == current_user.id).first()
|
||||
if not assignment:
|
||||
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="You are not assigned to this chore")
|
||||
|
||||
completion_log = ChoreCompletionLog(chore_id=chore_id, user_id=current_user.id, completed_at=datetime.utcnow(), notes=notes)
|
||||
db.add(completion_log)
|
||||
assignment.completed_at = datetime.utcnow()
|
||||
|
||||
all_assignments = db.query(ChoreAssignment).filter(ChoreAssignment.chore_id == chore_id).all()
|
||||
>>>>>>> 65c71b3d67d462fe9ecc01a1c2aa17e54b626fe2
|
||||
if all(a.completed_at is not None for a in all_assignments):
|
||||
chore.completed_at = datetime.utcnow()
|
||||
chore.status = "completed"
|
||||
|
||||
db.commit()
|
||||
db.refresh(completion_log)
|
||||
<<<<<<< HEAD
|
||||
|
||||
=======
|
||||
>>>>>>> 65c71b3d67d462fe9ecc01a1c2aa17e54b626fe2
|
||||
return enrich_completion_log(db, completion_log)
|
||||
|
||||
|
||||
@router.get("/completions", response_model=List[log_schemas.ChoreCompletionLog])
|
||||
<<<<<<< HEAD
|
||||
def get_completion_logs(
|
||||
skip: int = 0,
|
||||
limit: int = 100,
|
||||
@@ -148,10 +175,21 @@ def get_completion_logs(
|
||||
logs = query.offset(skip).limit(limit).all()
|
||||
|
||||
# Enrich with related data
|
||||
=======
|
||||
def get_completion_logs(skip: int = 0, limit: int = 100, chore_id: Optional[int] = Query(None), user_id: Optional[int] = Query(None), start_date: Optional[datetime] = Query(None), end_date: Optional[datetime] = Query(None), db: Session = Depends(get_db), current_user: User = Depends(get_current_user)):
|
||||
query = db.query(ChoreCompletionLog)
|
||||
if chore_id: query = query.filter(ChoreCompletionLog.chore_id == chore_id)
|
||||
if user_id: query = query.filter(ChoreCompletionLog.user_id == user_id)
|
||||
if start_date: query = query.filter(ChoreCompletionLog.completed_at >= start_date)
|
||||
if end_date: query = query.filter(ChoreCompletionLog.completed_at <= end_date)
|
||||
query = query.order_by(ChoreCompletionLog.completed_at.desc())
|
||||
logs = query.offset(skip).limit(limit).all()
|
||||
>>>>>>> 65c71b3d67d462fe9ecc01a1c2aa17e54b626fe2
|
||||
return [enrich_completion_log(db, log) for log in logs]
|
||||
|
||||
|
||||
@router.get("/reports/weekly", response_model=log_schemas.WeeklyChoreReport)
|
||||
<<<<<<< HEAD
|
||||
def get_weekly_report(
|
||||
user_id: Optional[int] = Query(None, description="Get report for specific user (omit for family-wide)"),
|
||||
weeks_ago: int = Query(0, description="Number of weeks ago (0 = current week)"),
|
||||
@@ -173,10 +211,14 @@ def get_weekly_report(
|
||||
- Recent completions
|
||||
"""
|
||||
# Calculate week boundaries
|
||||
=======
|
||||
def get_weekly_report(user_id: Optional[int] = Query(None), weeks_ago: int = Query(0), db: Session = Depends(get_db), current_user: User = Depends(get_current_user)):
|
||||
>>>>>>> 65c71b3d67d462fe9ecc01a1c2aa17e54b626fe2
|
||||
today = datetime.now().replace(hour=0, minute=0, second=0, microsecond=0)
|
||||
start_of_week = today - timedelta(days=today.weekday()) - timedelta(weeks=weeks_ago)
|
||||
end_of_week = start_of_week + timedelta(days=7)
|
||||
|
||||
<<<<<<< HEAD
|
||||
# Base query
|
||||
query = db.query(ChoreCompletionLog).filter(
|
||||
and_(
|
||||
@@ -195,6 +237,12 @@ def get_weekly_report(
|
||||
total_completions = len(logs)
|
||||
|
||||
# Completions by user
|
||||
=======
|
||||
query = db.query(ChoreCompletionLog).filter(and_(ChoreCompletionLog.completed_at >= start_of_week, ChoreCompletionLog.completed_at < end_of_week))
|
||||
if user_id: query = query.filter(ChoreCompletionLog.user_id == user_id)
|
||||
logs = query.all()
|
||||
|
||||
>>>>>>> 65c71b3d67d462fe9ecc01a1c2aa17e54b626fe2
|
||||
completions_by_user = {}
|
||||
for log in logs:
|
||||
user = db.query(User).filter(User.id == log.user_id).first()
|
||||
@@ -202,6 +250,7 @@ def get_weekly_report(
|
||||
username = user.full_name or user.username
|
||||
completions_by_user[username] = completions_by_user.get(username, 0) + 1
|
||||
|
||||
<<<<<<< HEAD
|
||||
# Completions by chore
|
||||
completions_by_chore = {}
|
||||
for log in logs:
|
||||
@@ -210,11 +259,19 @@ def get_weekly_report(
|
||||
completions_by_chore[chore.title] = completions_by_chore.get(chore.title, 0) + 1
|
||||
|
||||
# Completions by day
|
||||
=======
|
||||
completions_by_chore = {}
|
||||
for log in logs:
|
||||
chore = db.query(Chore).filter(Chore.id == log.chore_id).first()
|
||||
if chore: completions_by_chore[chore.title] = completions_by_chore.get(chore.title, 0) + 1
|
||||
|
||||
>>>>>>> 65c71b3d67d462fe9ecc01a1c2aa17e54b626fe2
|
||||
completions_by_day = {}
|
||||
for log in logs:
|
||||
day_name = log.completed_at.strftime("%A")
|
||||
completions_by_day[day_name] = completions_by_day.get(day_name, 0) + 1
|
||||
|
||||
<<<<<<< HEAD
|
||||
# Top performers
|
||||
user_stats = []
|
||||
for user_name, count in completions_by_user.items():
|
||||
@@ -360,12 +417,59 @@ def delete_completion_log(
|
||||
detail="Not authorized to delete this completion log"
|
||||
)
|
||||
|
||||
=======
|
||||
user_stats = []
|
||||
for user_name, count in completions_by_user.items():
|
||||
user = db.query(User).filter((User.full_name == user_name) | (User.username == user_name)).first()
|
||||
user_stats.append({"username": user_name, "count": count, "avatar_url": user.avatar_url if user else None})
|
||||
user_stats.sort(key=lambda x: x["count"], reverse=True)
|
||||
top_performers = user_stats[:5]
|
||||
|
||||
recent_logs = sorted(logs, key=lambda x: x.completed_at, reverse=True)[:10]
|
||||
recent_completions = [enrich_completion_log(db, log) for log in recent_logs]
|
||||
|
||||
return {"start_date": start_of_week, "end_date": end_of_week, "total_completions": len(logs), "completions_by_user": completions_by_user, "completions_by_chore": completions_by_chore, "completions_by_day": completions_by_day, "top_performers": top_performers, "recent_completions": recent_completions}
|
||||
|
||||
|
||||
@router.get("/reports/user/{user_id}", response_model=log_schemas.UserChoreStats)
|
||||
def get_user_stats(user_id: int, db: Session = Depends(get_db), current_user: User = Depends(get_current_user)):
|
||||
user = db.query(User).filter(User.id == user_id).first()
|
||||
if not user: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="User not found")
|
||||
|
||||
total_completions = db.query(ChoreCompletionLog).filter(ChoreCompletionLog.user_id == user_id).count()
|
||||
|
||||
today = datetime.now().replace(hour=0, minute=0, second=0, microsecond=0)
|
||||
start_of_week = today - timedelta(days=today.weekday())
|
||||
completions_this_week = db.query(ChoreCompletionLog).filter(and_(ChoreCompletionLog.user_id == user_id, ChoreCompletionLog.completed_at >= start_of_week)).count()
|
||||
|
||||
start_of_month = today.replace(day=1)
|
||||
completions_this_month = db.query(ChoreCompletionLog).filter(and_(ChoreCompletionLog.user_id == user_id, ChoreCompletionLog.completed_at >= start_of_month)).count()
|
||||
|
||||
favorite_chore = None
|
||||
chore_counts = db.query(ChoreCompletionLog.chore_id, func.count(ChoreCompletionLog.id).label('count')).filter(ChoreCompletionLog.user_id == user_id).group_by(ChoreCompletionLog.chore_id).order_by(func.count(ChoreCompletionLog.id).desc()).first()
|
||||
if chore_counts:
|
||||
chore = db.query(Chore).filter(Chore.id == chore_counts[0]).first()
|
||||
if chore: favorite_chore = chore.title
|
||||
|
||||
recent_logs = db.query(ChoreCompletionLog).filter(ChoreCompletionLog.user_id == user_id).order_by(ChoreCompletionLog.completed_at.desc()).limit(10).all()
|
||||
recent_completions = [enrich_completion_log(db, log) for log in recent_logs]
|
||||
|
||||
return {"user_id": user.id, "username": user.username, "full_name": user.full_name, "avatar_url": user.avatar_url, "total_completions": total_completions, "completions_this_week": completions_this_week, "completions_this_month": completions_this_month, "favorite_chore": favorite_chore, "recent_completions": recent_completions}
|
||||
|
||||
|
||||
@router.delete("/completions/{log_id}", status_code=status.HTTP_204_NO_CONTENT)
|
||||
def delete_completion_log(log_id: int, db: Session = Depends(get_db), current_user: User = Depends(get_current_user)):
|
||||
log = db.query(ChoreCompletionLog).filter(ChoreCompletionLog.id == log_id).first()
|
||||
if not log: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Completion log not found")
|
||||
if not current_user.is_admin and log.user_id != current_user.id: raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Not authorized")
|
||||
>>>>>>> 65c71b3d67d462fe9ecc01a1c2aa17e54b626fe2
|
||||
db.delete(log)
|
||||
db.commit()
|
||||
return None
|
||||
|
||||
|
||||
@router.post("/completions/{log_id}/verify", response_model=log_schemas.ChoreCompletionLog)
|
||||
<<<<<<< HEAD
|
||||
def verify_completion(
|
||||
log_id: int,
|
||||
db: Session = Depends(get_db),
|
||||
@@ -394,4 +498,13 @@ def verify_completion(
|
||||
db.commit()
|
||||
db.refresh(log)
|
||||
|
||||
=======
|
||||
def verify_completion(log_id: int, db: Session = Depends(get_db), current_user: User = Depends(get_current_user)):
|
||||
log = db.query(ChoreCompletionLog).filter(ChoreCompletionLog.id == log_id).first()
|
||||
if not log: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Completion log not found")
|
||||
if log.user_id == current_user.id: raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Cannot verify own completion")
|
||||
log.verified_by_user_id = current_user.id
|
||||
db.commit()
|
||||
db.refresh(log)
|
||||
>>>>>>> 65c71b3d67d462fe9ecc01a1c2aa17e54b626fe2
|
||||
return enrich_completion_log(db, log)
|
||||
@@ -1,3 +1,4 @@
|
||||
<<<<<<< HEAD
|
||||
"""Application configuration."""
|
||||
from pydantic_settings import BaseSettings
|
||||
from typing import List
|
||||
@@ -40,3 +41,6 @@ class Settings(BaseSettings):
|
||||
return self.ALLOWED_ORIGINS
|
||||
|
||||
settings = Settings()
|
||||
=======
|
||||
IiIiQXBwbGljYXRpb24gY29uZmlndXJhdGlvbi4iIiIKZnJvbSBweWRhbnRpY19zZXR0aW5ncyBpbXBvcnQgQmFzZVNldHRpbmdzCmZyb20gdHlwaW5nIGltcG9ydCBMaXN0CgoKY2xhc3MgU2V0dGluZ3MoQmFzZVNldHRpbmdzKToKICAgICIiIkFwcGxpY2F0aW9uIHNldHRpbmdzLiIiIgogICAgCiAgICBBUFBfTkFNRTogc3RyID0gIkZhbWlseSBIdWIiCiAgICBBUFBfVkVSU0lPTjogc3RyID0gIjAuMS4wIgogICAgREVCVUc6IGJvb2wgPSBUcnVlCiAgICAKICAgICMgRGF0YWJhc2UKICAgIERBVEFCQVNFX1VSTDogc3RyID0gInNxbGl0ZTovLy8uL2ZhbWlseV9odWIuZGIiCiAgICAKICAgICMgU2VjdXJpdHkKICAgIFNFQ1JFVF9LRVk6IHN0ciA9ICJ5b3VyLXNlY3JldC1rZXktY2hhbmdlLXRoaXMtaW4tcHJvZHVjdGlvbiIKICAgIEFMR09SSVRITTogc3RyID0gIkhTMjU2IgogICAgQUNDRVNTX1RPS0VOX0VYUElSRV9NSU5VVEVTOiBpbnQgPSAzMAogICAgCiAgICAjIEVudmlyb25tZW50CiAgICBFTlZJUk9OTUVOVDogc3RyID0gImRldmVsb3BtZW50IgogICAgCiAgICAjIENPUlMgLSBhY2NlcHRzIGVpdGhlciBjb21tYS1zZXBhcmF0ZWQgc3RyaW5nIG9yIEpTT04gYXJyYXkKICAgIENPUlNfT1JJR0lOUzogc3RyID0gImh0dHA6Ly9sb2NhbGhvc3Q6NTE3MyxodHRwOi8vbG9jYWxob3N0OjMwMDAsaHR0cDovLzEwLjAuMC4xMjc6NTE3MyIKICAgIAogICAgY2xhc3MgQ29uZmlnOgogICAgICAgIGVudl9maWxlID0gIi5lbnYiCiAgICAgICAgY2FzZV9zZW5zaXRpdmUgPSBUcnVlCiAgICAKICAgIEBwcm9wZXJ0eQogICAgZGVmIGNvcnNfb3JpZ2lucyhzZWxmKSAtPiBMaXN0W3N0cl06CiAgICAgICAgIiIiUGFyc2UgQ09SU19PUklHSU5TIGludG8gYSBsaXN0LiIiIgogICAgICAgIGlmIGlzaW5zdGFuY2Uoc2VsZi5DT1JTX09SSUdJTlMsIHN0cik6CiAgICAgICAgICAgIHJldHVybiBbb3JpZ2luLnN0cmlwKCkgZm9yIG9yaWdpbiBpbiBzZWxmLkNPUlNfT1JJR0lOUy5zcGxpdCgnLCcpXQogICAgICAgIHJldHVybiBzZWxmLkNPUlNfT1JJR0lOUwoKCnNldHRpbmdzID0gU2V0dGluZ3MoKQo=
|
||||
>>>>>>> 65c71b3d67d462fe9ecc01a1c2aa17e54b626fe2
|
||||
@@ -1,10 +1,15 @@
|
||||
"""Main FastAPI application."""
|
||||
from fastapi import FastAPI
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
<<<<<<< HEAD
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
from pathlib import Path
|
||||
from app.core.config import settings
|
||||
from app.api.v1 import auth, users, chores, uploads, public, chore_logs
|
||||
=======
|
||||
from app.core.config import settings
|
||||
from app.api.v1 import auth, users, chores
|
||||
>>>>>>> 65c71b3d67d462fe9ecc01a1c2aa17e54b626fe2
|
||||
|
||||
# Create FastAPI app
|
||||
app = FastAPI(
|
||||
@@ -15,12 +20,15 @@ app = FastAPI(
|
||||
)
|
||||
|
||||
# Configure CORS
|
||||
<<<<<<< HEAD
|
||||
print("="*70)
|
||||
print("FAMILY HUB - CORS CONFIGURATION")
|
||||
print("="*70)
|
||||
print(f"Allowed Origins: {settings.cors_origins}")
|
||||
print("="*70)
|
||||
|
||||
=======
|
||||
>>>>>>> 65c71b3d67d462fe9ecc01a1c2aa17e54b626fe2
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=settings.cors_origins,
|
||||
@@ -29,18 +37,24 @@ app.add_middleware(
|
||||
allow_headers=["*"],
|
||||
)
|
||||
|
||||
<<<<<<< HEAD
|
||||
# Mount static files for uploads
|
||||
static_path = Path(__file__).parent / "static"
|
||||
static_path.mkdir(exist_ok=True)
|
||||
app.mount("/static", StaticFiles(directory=str(static_path)), name="static")
|
||||
|
||||
=======
|
||||
>>>>>>> 65c71b3d67d462fe9ecc01a1c2aa17e54b626fe2
|
||||
# Include routers
|
||||
app.include_router(auth.router, prefix="/api/v1/auth", tags=["authentication"])
|
||||
app.include_router(users.router, prefix="/api/v1/users", tags=["users"])
|
||||
app.include_router(chores.router, prefix="/api/v1/chores", tags=["chores"])
|
||||
<<<<<<< HEAD
|
||||
app.include_router(chore_logs.router, prefix="/api/v1/chores", tags=["chore-logs"])
|
||||
app.include_router(uploads.router, prefix="/api/v1/uploads", tags=["uploads"])
|
||||
app.include_router(public.router, prefix="/api/v1/public", tags=["public"])
|
||||
=======
|
||||
>>>>>>> 65c71b3d67d462fe9ecc01a1c2aa17e54b626fe2
|
||||
|
||||
@app.get("/")
|
||||
async def root():
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
# Models package
|
||||
from app.models.user import User
|
||||
from app.models.chore import Chore
|
||||
<<<<<<< HEAD
|
||||
from app.models.chore_assignment import ChoreAssignment
|
||||
from app.models.chore_completion_log import ChoreCompletionLog
|
||||
|
||||
__all__ = ["User", "Chore", "ChoreAssignment", "ChoreCompletionLog"]
|
||||
__all__ = ["User", "Chore", "ChoreAssignment", "ChoreCompletionLog"]
|
||||
=======
|
||||
|
||||
__all__ = ["User", "Chore"]
|
||||
>>>>>>> 65c71b3d67d462fe9ecc01a1c2aa17e54b626fe2
|
||||
@@ -33,11 +33,19 @@ class Chore(Base):
|
||||
title = Column(String(200), nullable=False)
|
||||
description = Column(String(500))
|
||||
room = Column(String(50)) # bedroom1, bedroom2, kitchen, bathroom1, etc.
|
||||
<<<<<<< HEAD
|
||||
frequency = Column(SQLEnum(ChoreFrequency, values_callable=lambda x: [e.value for e in x]), nullable=False)
|
||||
points = Column(Integer, default=0) # Points awarded for completing the chore
|
||||
image_url = Column(String(500)) # URL to chore image
|
||||
assignment_type = Column(SQLEnum(ChoreAssignmentType, values_callable=lambda x: [e.value for e in x]), default=ChoreAssignmentType.ANY_ONE) # How chore should be completed
|
||||
status = Column(SQLEnum(ChoreStatus, values_callable=lambda x: [e.value for e in x]), default=ChoreStatus.PENDING)
|
||||
=======
|
||||
frequency = Column(SQLEnum(ChoreFrequency), nullable=False)
|
||||
points = Column(Integer, default=0) # Points awarded for completing the chore
|
||||
image_url = Column(String(500)) # URL to chore image
|
||||
assignment_type = Column(SQLEnum(ChoreAssignmentType), default=ChoreAssignmentType.ANY_ONE) # How chore should be completed
|
||||
status = Column(SQLEnum(ChoreStatus), default=ChoreStatus.PENDING)
|
||||
>>>>>>> 65c71b3d67d462fe9ecc01a1c2aa17e54b626fe2
|
||||
assigned_user_id = Column(Integer, ForeignKey("users.id")) # Deprecated - use assignments instead
|
||||
due_date = Column(DateTime)
|
||||
completed_at = Column(DateTime)
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
"""User model."""
|
||||
<<<<<<< HEAD
|
||||
from sqlalchemy import Boolean, Column, Integer, String, DateTime, Date
|
||||
=======
|
||||
from sqlalchemy import Boolean, Column, Integer, String, DateTime
|
||||
>>>>>>> 65c71b3d67d462fe9ecc01a1c2aa17e54b626fe2
|
||||
from sqlalchemy.orm import relationship
|
||||
from datetime import datetime
|
||||
from app.core.database import Base
|
||||
@@ -15,8 +19,11 @@ class User(Base):
|
||||
hashed_password = Column(String(200), nullable=False)
|
||||
discord_id = Column(String(100)) # For Discord integration
|
||||
profile_picture = Column(String(500)) # URL to profile picture
|
||||
<<<<<<< HEAD
|
||||
avatar_url = Column(String(500)) # URL to uploaded avatar
|
||||
birthday = Column(Date, nullable=True) # Birthday for chore logic
|
||||
=======
|
||||
>>>>>>> 65c71b3d67d462fe9ecc01a1c2aa17e54b626fe2
|
||||
is_active = Column(Boolean, default=True)
|
||||
is_admin = Column(Boolean, default=False)
|
||||
created_at = Column(DateTime, default=datetime.utcnow)
|
||||
@@ -24,5 +31,8 @@ class User(Base):
|
||||
|
||||
# Relationships (lazy loaded to avoid circular imports)
|
||||
chores = relationship("Chore", back_populates="assigned_user", lazy="select")
|
||||
<<<<<<< HEAD
|
||||
chore_assignments = relationship("ChoreAssignment", back_populates="user", lazy="select")
|
||||
chore_completion_logs = relationship("ChoreCompletionLog", foreign_keys="[ChoreCompletionLog.user_id]", back_populates="user", lazy="select")
|
||||
=======
|
||||
>>>>>>> 65c71b3d67d462fe9ecc01a1c2aa17e54b626fe2
|
||||
@@ -1,4 +1,7 @@
|
||||
# Schemas package
|
||||
<<<<<<< HEAD
|
||||
from app.schemas import auth, chore, user, chore_completion_log
|
||||
|
||||
__all__ = ["auth", "chore", "user", "chore_completion_log"]
|
||||
=======
|
||||
>>>>>>> 65c71b3d67d462fe9ecc01a1c2aa17e54b626fe2
|
||||
@@ -1,9 +1,16 @@
|
||||
"""Chore schemas."""
|
||||
from pydantic import BaseModel, ConfigDict, field_validator
|
||||
<<<<<<< HEAD
|
||||
from typing import Optional, Union, List
|
||||
from datetime import datetime, date
|
||||
|
||||
from app.models.chore import ChoreFrequency, ChoreStatus, ChoreAssignmentType
|
||||
=======
|
||||
from typing import Optional, Union
|
||||
from datetime import datetime, date
|
||||
|
||||
from app.models.chore import ChoreFrequency, ChoreStatus
|
||||
>>>>>>> 65c71b3d67d462fe9ecc01a1c2aa17e54b626fe2
|
||||
|
||||
|
||||
class ChoreBase(BaseModel):
|
||||
@@ -12,17 +19,26 @@ class ChoreBase(BaseModel):
|
||||
description: Optional[str] = None
|
||||
room: str
|
||||
frequency: ChoreFrequency
|
||||
<<<<<<< HEAD
|
||||
points: Optional[int] = 0
|
||||
image_url: Optional[str] = None
|
||||
assignment_type: Optional[ChoreAssignmentType] = ChoreAssignmentType.ANY_ONE
|
||||
=======
|
||||
assigned_user_id: Optional[int] = None
|
||||
>>>>>>> 65c71b3d67d462fe9ecc01a1c2aa17e54b626fe2
|
||||
due_date: Optional[Union[datetime, date, str]] = None
|
||||
|
||||
@field_validator('due_date', mode='before')
|
||||
@classmethod
|
||||
def parse_due_date(cls, v):
|
||||
"""Parse due_date to handle various formats."""
|
||||
<<<<<<< HEAD
|
||||
if v is None or v == '' or isinstance(v, (datetime, date)):
|
||||
return None if v == '' else v
|
||||
=======
|
||||
if v is None or isinstance(v, (datetime, date)):
|
||||
return v
|
||||
>>>>>>> 65c71b3d67d462fe9ecc01a1c2aa17e54b626fe2
|
||||
if isinstance(v, str):
|
||||
# Try parsing as datetime first
|
||||
for fmt in ['%Y-%m-%dT%H:%M:%S', '%Y-%m-%d']:
|
||||
@@ -30,14 +46,22 @@ class ChoreBase(BaseModel):
|
||||
return datetime.strptime(v, fmt)
|
||||
except ValueError:
|
||||
continue
|
||||
<<<<<<< HEAD
|
||||
# If no format matches, return None instead of the invalid string
|
||||
return None
|
||||
return None
|
||||
=======
|
||||
return v
|
||||
>>>>>>> 65c71b3d67d462fe9ecc01a1c2aa17e54b626fe2
|
||||
|
||||
|
||||
class ChoreCreate(ChoreBase):
|
||||
"""Schema for creating a chore."""
|
||||
<<<<<<< HEAD
|
||||
assigned_user_ids: Optional[List[int]] = [] # Multiple users can be assigned
|
||||
=======
|
||||
pass
|
||||
>>>>>>> 65c71b3d67d462fe9ecc01a1c2aa17e54b626fe2
|
||||
|
||||
|
||||
class ChoreUpdate(BaseModel):
|
||||
@@ -46,18 +70,28 @@ class ChoreUpdate(BaseModel):
|
||||
description: Optional[str] = None
|
||||
room: Optional[str] = None
|
||||
frequency: Optional[ChoreFrequency] = None
|
||||
<<<<<<< HEAD
|
||||
points: Optional[int] = None
|
||||
status: Optional[ChoreStatus] = None
|
||||
assignment_type: Optional[ChoreAssignmentType] = None
|
||||
assigned_user_ids: Optional[List[int]] = None # Multiple users
|
||||
=======
|
||||
status: Optional[ChoreStatus] = None
|
||||
assigned_user_id: Optional[int] = None
|
||||
>>>>>>> 65c71b3d67d462fe9ecc01a1c2aa17e54b626fe2
|
||||
due_date: Optional[Union[datetime, date, str]] = None
|
||||
|
||||
@field_validator('due_date', mode='before')
|
||||
@classmethod
|
||||
def parse_due_date(cls, v):
|
||||
"""Parse due_date to handle various formats."""
|
||||
<<<<<<< HEAD
|
||||
if v is None or v == '' or isinstance(v, (datetime, date)):
|
||||
return None if v == '' else v
|
||||
=======
|
||||
if v is None or isinstance(v, (datetime, date)):
|
||||
return v
|
||||
>>>>>>> 65c71b3d67d462fe9ecc01a1c2aa17e54b626fe2
|
||||
if isinstance(v, str):
|
||||
# Try parsing as datetime first
|
||||
for fmt in ['%Y-%m-%dT%H:%M:%S', '%Y-%m-%d']:
|
||||
@@ -65,6 +99,7 @@ class ChoreUpdate(BaseModel):
|
||||
return datetime.strptime(v, fmt)
|
||||
except ValueError:
|
||||
continue
|
||||
<<<<<<< HEAD
|
||||
# If no format matches, return None instead of the invalid string
|
||||
return None
|
||||
return None
|
||||
@@ -72,14 +107,24 @@ class ChoreUpdate(BaseModel):
|
||||
|
||||
class AssignedUserDetail(BaseModel):
|
||||
"""User info for chore assignment."""
|
||||
=======
|
||||
return v
|
||||
|
||||
|
||||
class AssignedUser(BaseModel):
|
||||
"""Minimal user info for chore assignment."""
|
||||
>>>>>>> 65c71b3d67d462fe9ecc01a1c2aa17e54b626fe2
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
id: int
|
||||
username: str
|
||||
full_name: str
|
||||
<<<<<<< HEAD
|
||||
avatar_url: Optional[str] = None
|
||||
birthday: Optional[date] = None
|
||||
completed_at: Optional[datetime] = None # When this user completed the chore
|
||||
=======
|
||||
>>>>>>> 65c71b3d67d462fe9ecc01a1c2aa17e54b626fe2
|
||||
|
||||
|
||||
class Chore(ChoreBase):
|
||||
@@ -88,6 +133,7 @@ class Chore(ChoreBase):
|
||||
|
||||
id: int
|
||||
status: ChoreStatus
|
||||
<<<<<<< HEAD
|
||||
points: int
|
||||
assignment_type: ChoreAssignmentType
|
||||
assigned_users: List[AssignedUserDetail] = [] # Multiple users with completion status
|
||||
@@ -97,3 +143,9 @@ class Chore(ChoreBase):
|
||||
|
||||
# Legacy field for backward compatibility
|
||||
assigned_user_id: Optional[int] = None
|
||||
=======
|
||||
assigned_user: Optional[AssignedUser] = None
|
||||
completed_at: Optional[datetime] = None
|
||||
created_at: datetime
|
||||
updated_at: datetime
|
||||
>>>>>>> 65c71b3d67d462fe9ecc01a1c2aa17e54b626fe2
|
||||
@@ -1,3 +1,4 @@
|
||||
<<<<<<< HEAD
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Database initialization script for Family Hub.
|
||||
@@ -226,3 +227,6 @@ def init_db():
|
||||
|
||||
if __name__ == "__main__":
|
||||
init_db()
|
||||
=======
|
||||
IyEvdXNyL2Jpbi9lbnYgcHl0aG9uMwoiIiIKRGF0YWJhc2UgaW5pdGlhbGl6YXRpb24gc2NyaXB0IGZvciBGYW1pbHkgSHViLgpDcmVhdGVzIHRhYmxlcyBhbmQgcG9wdWxhdGVzIHdpdGggZGVtbyBkYXRhLgoiIiIKCmltcG9ydCBzeXMKZnJvbSBwYXRobGliIGltcG9ydCBQYXRoCmZyb20gZGF0ZXRpbWUgaW1wb3J0IGRhdGV0aW1lLCB0aW1lZGVsdGEKCiMgQWRkIHBhcmVudCBkaXJlY3RvcnkgdG8gcGF0aCB0byBpbXBvcnQgZnJvbSBhcHAKc3lzLnBhdGguaW5zZXJ0KDAsIHN0cihQYXRoKF9fZmlsZV9fKS5wYXJlbnQpKQoKZnJvbSBhcHAuY29yZS5kYXRhYmFzZSBpbXBvcnQgZW5naW5lLCBTZXNzaW9uTG9jYWwsIEJhc2UKZnJvbSBhcHAubW9kZWxzIGltcG9ydCBVc2VyLCBDaG9yZQpmcm9tIHBhc3NsaWIuY29udGV4dCBpbXBvcnQgQ3J5cHRDb250ZXh0Cgpwd2RfY29udGV4dCA9IENyeXB0Q29udGV4dChzY2hlbWVzPVsiYmNyeXB0Il0sIGRlcHJlY2F0ZWQ9ImF1dG8iKQoKZGVmIGluaXRfZGIoKToKICAgICIiIkluaXRpYWxpemUgdGhlIGRhdGFiYXNlIHdpdGggdGFibGVzIGFuZCBkZW1vIGRhdGEuIiIiCiAgICBwcmludCgi8J+agCBJbml0aWFsaXppbmcgRmFtaWx5IEh1YiBkYXRhYmFzZS4uLiIpCiAgICAKICAgICMgQ3JlYXRlIGFsbCB0YWJsZXMKICAgIHByaW50KCLwn5SnIENyZWF0aW5nIGRhdGFiYXNlIHRhYmxlcy4uLiIpCiAgICBCYXNlLm1ldGFkYXRhLmNyZWF0ZV9hbGwoYmluZD1lbmdpbmUpCiAgICAKICAgIGRiID0gU2Vzc2lvbkxvY2FsKCkKICAgIAogICAgdHJ5OgogICAgICAgICMgQ2hlY2sgaWYgZGF0YSBhbHJlYWR5IGV4aXN0cwogICAgICAgIGV4aXN0aW5nX3VzZXJzID0gZGIucXVlcnkoVXNlcikuY291bnQoKQogICAgICAgIGlmIGV4aXN0aW5nX3VzZXJzID4gMDoKICAgICAgICAgICAgcHJpbnQoZiLimqDvuI8gIERhdGFiYXNlIGFscmVhZHkgaGFzIHtleGlzdGluZ191c2Vyc30gdXNlcnMuIFNraXBwaW5nIGluaXRpYWxpemF0aW9uLiIpCiAgICAgICAgICAgIHByaW50KCIgICBUbyByZXNldCB0aGUgZGF0YWJhc2UsIGRlbGV0ZSB0aGUgZmlsZSBhbmQgcnVuIHRoaXMgc2NyaXB0IGFnYWluLiIpCiAgICAgICAgICAgIHJldHVybgogICAgICAgIAogICAgICAgICMgQ3JlYXRlIGRlbW8gdXNlcnMKICAgICAgICBwcmludCgiXG7wn5GlIENyZWF0aW5nIGRlbW8gdXNlcnMuLi4iKQogICAgICAgIHVzZXJzX2RhdGEgPSBbCiAgICAgICAgICAgIHsidXNlcm5hbWUiOiAiamVzcyIsICJlbWFpbCI6ICJqZXNzQGZhbWlseS5sb2NhbCIsICJmdWxsX25hbWUiOiAiSmVzcyIsICJpc19hZG1pbiI6IFRydWV9LAogICAgICAgICAgICB7InVzZXJuYW1lIjogImxvdSIsICJlbWFpbCI6ICJsb3VAZmFtaWx5LmxvY2FsIiwgImZ1bGxfbmFtZSI6ICJMb3UiLCAiaXNfYWRtaW4iOiBGYWxzZX0sCiAgICAgICAgICAgIHsidXNlcm5hbWUiOiAid2lsbGlhbSIsICJlbWFpbCI6ICJ3aWxsaWFtQGZhbWlseS5sb2NhbCIsICJmdWxsX25hbWUiOiAiV2lsbGlhbSIsICJpc19hZG1pbiI6IEZhbHNlfSwKICAgICAgICAgICAgeyJ1c2VybmFtZSI6ICJ4YW5kZXIiLCAiZW1haWwiOiAieGFuZGVyQGZhbWlseS5sb2NhbCIsICJmdWxsX25hbWUiOiAiWGFuZGVyIiwgImlzX2FkbWluIjogRmFsc2V9LAogICAgICAgICAgICB7InVzZXJuYW1lIjogImJlbGxhIiwgImVtYWlsIjogImJlbGxhQGZhbWlseS5sb2NhbCIsICJmdWxsX25hbWUiOiAiQmVsbGEiLCAiaXNfYWRtaW4iOiBGYWxzZX0sCiAgICAgICAgXQogICAgICAgIAogICAgICAgIHVzZXJzID0gW10KICAgICAgICBmb3IgdXNlcl9kYXRhIGluIHVzZXJzX2RhdGE6CiAgICAgICAgICAgIHVzZXIgPSBVc2VyKAogICAgICAgICAgICAgICAgdXNlcm5hbWU9dXNlcl9kYXRhWyJ1c2VybmFtZSJdLAogICAgICAgICAgICAgICAgZW1haWw9dXNlcl9kYXRhWyJlbWFpbCJdLAogICAgICAgICAgICAgICAgZnVsbF9uYW1lPXVzZXJfZGF0YVsiZnVsbF9uYW1lIl0sCiAgICAgICAgICAgICAgICBoYXNoZWRfcGFzc3dvcmQ9cHdkX2NvbnRleHQuaGFzaCgicGFzc3dvcmQxMjMiKSwKICAgICAgICAgICAgICAgIGlzX2FkbWluPXVzZXJfZGF0YVsiaXNfYWRtaW4iXSwKICAgICAgICAgICAgICAgIGlzX2FjdGl2ZT1UcnVlCiAgICAgICAgICAgICkKICAgICAgICAgICAgZGIuYWRkKHVzZXIpCiAgICAgICAgICAgIHVzZXJzLmFwcGVuZCh1c2VyKQogICAgICAgICAgICBhZG1pbl9iYWRnZSA9ICIg8J+RkSIgaWYgdXNlci5pc19hZG1pbiBlbHNlICIiCiAgICAgICAgICAgIHByaW50KGYiICDinJMge3VzZXIuZnVsbF9uYW1lfSAoe3VzZXIudXNlcm5hbWV9KXthZG1pbl9iYWRnZX0iKQogICAgICAgIAogICAgICAgIGRiLmZsdXNoKCkgICMgRmx1c2ggdG8gZ2V0IHVzZXIgSURzCiAgICAgICAgCiAgICAgICAgIyBDcmVhdGUgZGVtbyBjaG9yZXMKICAgICAgICBwcmludCgiXG7wn6e5IENyZWF0aW5nIGRlbW8gY2hvcmVzLi4uIikKICAgICAgICAKICAgICAgICAjIEdldCB1c2VyIElEcyBmb3IgYXNzaWdubWVudAogICAgICAgIGplc3MgPSBuZXh0KHUgZm9yIHUgaW4gdXNlcnMgaWYgdS51c2VybmFtZSA9PSAiamVzcyIpCiAgICAgICAgbG91ID0gbmV4dCh1IGZvciB1IGluIHVzZXJzIGlmIHUudXNlcm5hbWUgPT0gImxvdSIpCiAgICAgICAgd2lsbGlhbSA9IG5leHQodSBmb3IgdSBpbiB1c2VycyBpZiB1LnVzZXJuYW1lID09ICJ3aWxsaWFtIikKICAgICAgICB4YW5kZXIgPSBuZXh0KHUgZm9yIHUgaW4gdXNlcnMgaWYgdS51c2VybmFtZSA9PSAieGFuZGVyIikKICAgICAgICBiZWxsYSA9IG5leHQodSBmb3IgdSBpbiB1c2VycyBpZiB1LnVzZXJuYW1lID09ICJiZWxsYSIpCiAgICAgICAgCiAgICAgICAgZGVtb19jaG9yZXMgPSBbCiAgICAgICAgICAgICMgRGFpbHkgY2hvcmVzCiAgICAgICAgICAgIHsKICAgICAgICAgICAgICAgICJ0aXRsZSI6ICJGZWVkIHRoZSBwZXRzIiwKICAgICAgICAgICAgICAgICJkZXNjcmlwdGlvbiI6ICJGZWVkIGNhdHMgaW4gdGhlIG1vcm5pbmcgYW5kIGV2ZW5pbmciLAogICAgICAgICAgICAgICAgImZyZXF1ZW5jeSI6ICJEQUlMWSIsCiAgICAgICAgICAgICAgICAicG9pbnRzIjogNSwKICAgICAgICAgICAgICAgICJyb29tIjogIktpdGNoZW4iLAogICAgICAgICAgICAgICAgImFzc2lnbmVkX3VzZXJfaWQiOiBiZWxsYS5pZCwKICAgICAgICAgICAgICAgICJzdGF0dXMiOiAiUEVORElORyIKICAgICAgICAgICAgfSwKICAgICAgICAgICAgewogICAgICAgICAgICAgICAgInRpdGxlIjogIlRha2Ugb3V0IHRyYXNoIiwKICAgICAgICAgICAgICAgICJkZXNjcmlwdGlvbiI6ICJFbXB0eSBraXRjaGVuIGFuZCBiYXRocm9vbSBiaW5zIiwKICAgICAgICAgICAgICAgICJmcmVxdWVuY3kiOiAiREFJTFkiLAogICAgICAgICAgICAgICAgInBvaW50cyI6IDMsCiAgICAgICAgICAgICAgICAicm9vbSI6ICJLaXRjaGVuIiwKICAgICAgICAgICAgICAgICJhc3NpZ25lZF91c2VyX2lkIjogeGFuZGVyLmlkLAogICAgICAgICAgICAgICAgInN0YXR1cyI6ICJQRU5ESU5HIgogICAgICAgICAgICB9LAogICAgICAgICAgICB7CiAgICAgICAgICAgICAgICAidGl0bGUiOiAiVGlkeSBsaXZpbmcgcm9vbSIsCiAgICAgICAgICAgICAgICAiZGVzY3JpcHRpb24iOiAiUGljayB1cCB0b3lzLCBzdHJhaWdodGVuIGN1c2hpb25zLCBjbGVhciBjb2ZmZWUgdGFibGUiLAogICAgICAgICAgICAgICAgImZyZXF1ZW5jeSI6ICJEQUlMWSIsCiALine truncated
|
||||
>>>>>>> 65c71b3d67d462fe9ecc01a1c2aa17e54b626fe2
|
||||
@@ -1,3 +1,4 @@
|
||||
<<<<<<< HEAD
|
||||
import sys
|
||||
import os
|
||||
from datetime import datetime, timedelta
|
||||
@@ -225,3 +226,6 @@ def init_db():
|
||||
|
||||
if __name__ == "__main__":
|
||||
init_db()
|
||||
=======
|
||||
aW1wb3J0IHN5cwppbXBvcnQgb3MKZnJvbSBkYXRldGltZSBpbXBvcnQgZGF0ZXRpbWUsIHRpbWVkZWx0YQoKIyBBZGQgdGhlIGFwcCBkaXJlY3RvcnkgdG8gdGhlIHBhdGgKc3lzLnBhdGguaW5zZXJ0KDAsICcvYXBwJykKCmZyb20gc3FsYWxjaGVteSBpbXBvcnQgY3JlYXRlX2VuZ2luZQpmcm9tIHNxbGFsY2hlbXkub3JtIGltcG9ydCBzZXNzaW9ubWFrZXIKZnJvbSBhcHAuZGIuYmFzZSBpbXBvcnQgQmFzZQpmcm9tIGFwcC5tb2RlbHMudXNlciBpbXBvcnQgVXNlcgpmcm9tIGFwcC5tb2RlbHMuY2hvcmUgaW1wb3J0IENob3JlCmZyb20gYXBwLmNvcmUuc2VjdXJpdHkgaW1wb3J0IGdldF9wYXNzd29yZF9oYXNoCgojIERhdGFiYXNlIHNldHVwCkRBVEFCQVNFX1VSTCA9ICJzcWxpdGU6Ly8vLi9mYW1pbHlfaHViLmRiIgplbmdpbmUgPSBjcmVhdGVfZW5naW5lKERBVEFCQVNFX1VSTCwgY29ubmVjdF9hcmdzPXsiY2hlY2tfc2FtZV90aHJlYWQiOiBGYWxzZX0pClNlc3Npb25Mb2NhbCA9IHNlc3Npb25tYWtlcihhdXRvY29tbWl0PUZhbHNlLCBhdXRvZmx1c2g9RmFsc2UsIGJpbmQ9ZW5naW5lKQoKZGVmIGluaXRfZGIoKToKICAgICIiIkluaXRpYWxpemUgZGF0YWJhc2Ugd2l0aCBzY2hlbWEgYW5kIGRlbW8gZGF0YSIiIgogICAgcHJpbnQoIkNyZWF0aW5nIGFsbCB0YWJsZXMuLi4iKQogICAgQmFzZS5tZXRhZGF0YS5jcmVhdGVfYWxsKGJpbmQ9ZW5naW5lKQogICAgCiAgICBkYiA9IFNlc3Npb25Mb2NhbCgpCiAgICAKICAgIHRyeToKICAgICAgICAjIENoZWNrIGlmIHVzZXJzIGFscmVhZHkgZXhpc3QKICAgICAgICBleGlzdGluZ191c2VycyA9IGRiLnF1ZXJ5KFVzZXIpLmNvdW50KCkKICAgICAgICBpZiBleGlzdGluZ191c2VycyA+IDA6CiAgICAgICAgICAgIHByaW50KGYiRGF0YWJhc2UgYWxyZWFkeSBoYXMge2V4aXN0aW5nX3VzZXJzfSB1c2Vycy4gU2tpcHBpbmcgaW5pdGlhbGl6YXRpb24uIikKICAgICAgICAgICAgcmV0dXJuCiAgICAgICAgCiAgICAgICAgcHJpbnQoIkNyZWF0aW5nIGZhbWlseSBtZW1iZXJzLi4uIikKICAgICAgICAjIENyZWF0ZSBmYW1pbHkgbWVtYmVycwogICAgICAgIHVzZXJzX2RhdGEgPSBbCiAgICAgICAgICAgIHsidXNlcm5hbWUiOiAibG91IiwgImVtYWlsIjogImxvdUBmYW1pbHkubG9jYWwiLCAiZnVsbF9uYW1lIjogIkxvdSIsICJpc19hZG1pbiI6IEZhbHNlfSwKICAgICAgICAgICAgeyJ1c2VybmFtZSI6ICJqZXNzIiwgImVtYWlsIjogImplc3NAZmFtaWx5LmxvY2FsIiwgImZ1bGxfbmFtZSI6ICJKZXNzIiwgImlzX2FkbWluIjogVHJ1ZX0sCiAgICAgICAgICAgIHsidXNlcm5hbWUiOiAid2lsbGlhbSIsICJlbWFpbCI6ICJ3aWxsaWFtQGZhbWlseS5sb2NhbCIsICJmdWxsX25hbWUiOiAiV2lsbGlhbSIsICJpc19hZG1pbiI6IEZhbHNlfSwKICAgICAgICAgICAgeyJ1c2VybmFtZSI6ICJ4YW5kZXIiLCAiZW1haWwiOiAieGFuZGVyQGZhbWlseS5sb2NhbCIsICJmdWxsX25hbWUiOiAiWGFuZGVyIiwgImlzX2FkbWluIjogRmFsc2V9LAogICAgICAgICAgICB7InVzZXJuYW1lIjogImJlbGxhIiwgImVtYWlsIjogImJlbGxhQGZhbWlseS5sb2NhbCIsICJmdWxsX25hbWUiOiAiQmVsbGEiLCAiaXNfYWRtaW4iOiBGYWxzZX0sCiAgICAgICAgXQogICAgICAgIAogICAgICAgIHVzZXJzID0ge30KICAgICAgICBmb3IgdXNlcl9kYXRhIGluIHVzZXJzX2RhdGE6CiAgICAgICAgICAgIHVzZXIgPSBVc2VyKAogICAgICAgICAgICAgICAgdXNlcm5hbWU9dXNlcl9kYXRhWyJ1c2VybmFtZSJdLAogICAgICAgICAgICAgICAgZW1haWw9dXNlcl9kYXRhWyJlbWFpbCJdLAogICAgICAgICAgICAgICAgZnVsbF9uYW1lPXVzZXJfZGF0YVsiZnVsbF9uYW1lIl0sCiAgICAgICAgICAgICAgICBoYXNoZWRfcGFzc3dvcmQ9Z2V0X3Bhc3N3b3JkX2hhc2goInBhc3N3b3JkMTIzIiksCiAgICAgICAgICAgICAgICBpc19hZG1pbj11c2VyX2RhdGFbImlzX2FkbWluIl0sCiAgICAgICAgICAgICAgICBpc19hY3RpdmU9VHJ1ZSwKICAgICAgICAgICAgICAgIGRpc2NvcmRfaWQ9Tm9uZSwKICAgICAgICAgICAgICAgIHByb2ZpbGVfcGljdHVyZT1Ob25lCiAgICAgICAgICAgICkKICAgICAgICAgICAgZGIuYWRkKHVzZXIpCiAgICAgICAgICAgIHVzZXJzW3VzZXJfZGF0YVsidXNlcm5hbWUiXV0gPSB1c2VyCiAgICAgICAgICAgIHByaW50KGYiICDinJMgQ3JlYXRlZCB1c2VyOiB7dXNlcl9kYXRhWydmdWxsX25hbWUnXX0gKHt1c2VyX2RhdGFbJ3VzZXJuYW1lJ119KSIpCiAgICAgICAgCiAgICAgICAgZGIuY29tbWl0KCkKICAgICAgICAKICAgICAgICAjIFJlZnJlc2ggdG8gZ2V0IElEcwogICAgICAgIGZvciB1c2VyIGluIHVzZXJzLnZhbHVlcygpOgogICAgICAgICAgICBkYi5yZWZyZXNoKHVzZXIpCiAgICAgICAgCiAgICAgICAgcHJpbnQoIlxuQ3JlYXRpbmcgZGVtbyBjaG9yZXMuLi4iKQogICAgICAgICMgQ3JlYXRlIGRlbW8gY2hvcmVzIHdpdGggdmFyaW91cyBzdGF0dXNlcyBhbmQgYXNzaWdubWVudHMKICAgICAgICB0b2RheSA9IGRhdGV0aW1lLm5vdygpCiAgICAgICAgCiAgICAgICAgZGVtb19jaG9yZXMgPSBbCiAgICAgICAgICAgICMgRGFpbHkgY2hvcmVzCiAgICAgICAgICAgIHsKICAgICAgICAgICAgICAgICJ0aXRsZSI6ICJGZWVkIHRoZSBEb2ciLAogICAgICAgICAgICAgICAgImRlc2NyaXB0aW9uIjogIkdpdmUgUmV4IGhpcyBicmVha2Zhc3QgYW5kIGRpbm5lciIsCiAgICAgICAgICAgICAgICAicm9vbSI6ICJLaXRjaGVuIiwKICAgICAgICAgICAgICAgICJmcmVxdWVuY3kiOiAiREFJTFkiLAogICAgICAgICAgICAgICAgInN0YXR1cyI6ICJDT01QTEVURUQiLAogICAgICAgICAgICAgICAgImFzc2lnbmVkX3VzZXJfaWQiOiB1c2Vyc1sid2lsbGlhbSJdLmlkLAogICAgICAgICAgICAgICAgImR1ZV9kYXRlIjogdG9kYXkucmVwbGFjZShob3VyPTIzLCBtaW51dGU9NTksIHNlY29uZD01OSksCiAgICAgICAgICAgICAgICAiY29tcGxldGVkX2F0IjogdG9kYXkucmVwbGFjZShob3VyPTgsIG1pbnV0ZT0zMCkKICAgICAgICAgICAgfSwKICAgICAgICAgICAgewogICAgICAgICAgICAgICAgInRpdGxlIjogIlRha2UgT3V0IFRyYXNoIiwKICAgICAgICAgICAgICAgICJkZXNjcmlwdGlvbiI6ICJFbXB0eSBhbGwgYmlucyBhbmQgdGFrZSB0byBjdXJiIiwKICAgICAgICAgICAgICAgICJyb29tIjogIktpdGNoZW4iLAogICAgICAgICAgICAgICAgImZyZXF1ZW5jeSI6ICJEQUlMWSIsCiAgICAgICAgICAgICAgICAic3RhdHVzIjogIlBFTkRJTkciLAogICAgICAgICAgICAgICAgImFzc2lnbmVkX3VzZXJfaWQiOiB1c2Vyc1sieGFuZGVyIl0uaWQsCiAgICAgICAgICAgICAgICAiZHVlX2RhdGUiOiB0b2RheS5yZXBsYWNlKGhvdXI9MjMsIG1pbnV0ZT01OSwgc2Vjb25kPTU5KSwKICAgICAgICAgICAgICAgICJjb21wbGV0ZWRfYXQiOiBOb25lCiAgICAgICAgICAgIH0sCiAgICAgICAgICAgIHsKICAgICAgICAgICAgICAgICJ0aXRsZSI6ICJXYXNoIERpc2hlcyIsCiAgICAgICAgICAgICAgICAiZGVzY3JpcHRpb24iOiAiTG9hZCBhbmQgcnVuIHRoZSBkaXNod2FzaGVyIiwKICAgICAgICAgICAgICAgICJyb29tIjogIktpdGNoZW4iLAogICAgICAgICAgICAgICAgImZyZXF1ZW5jeSI6ICJEQUlMWSIsCiAgICAgICAgICAgICAgICAic3RhdHVzIjogIklOX1BST0dSRVNTIiwKICAgICAgICAgICAgICAgICJhc3NpZ25lZF91c2VyX2lkIjogdXNlcnNbImJlbGxhIl0Line truncated
|
||||
>>>>>>> 65c71b3d67d462fe9ecc01a1c2aa17e54b626fe2
|
||||
@@ -1,3 +1,4 @@
|
||||
<<<<<<< HEAD
|
||||
fastapi==0.115.0
|
||||
uvicorn[standard]==0.32.0
|
||||
sqlalchemy==2.0.36
|
||||
@@ -9,3 +10,6 @@ pydantic==2.10.3
|
||||
pydantic-settings==2.6.1
|
||||
python-dotenv==1.0.1
|
||||
email-validator==2.2.0
|
||||
=======
|
||||
ZmFzdGFwaT09MC4xMTUuMAp1dmljb3JuW3N0YW5kYXJkXT09MC4zMi4wCnNxbGFsY2hlbXk9PTIuMC4zNgpweXRob24tam9zZVtjcnlwdG9ncmFwaHldPT0zLjMuMApiY3J5cHQ9PTQuMi4wCnBhc3NsaWJbYmNyeXB0XT09MS43LjQKcHl0aG9uLW11bHRpcGFydD09MC4wLjEyCnB5ZGFudGljPT0yLjEwLjMKcHlkYW50aWMtc2V0dGluZ3M9PTIuNi4xCnB5dGhvbi1kb3RlbnY9PTEuMC4xCmVtYWlsLXZhbGlkYXRvcj09Mi4yLjAK
|
||||
>>>>>>> 65c71b3d67d462fe9ecc01a1c2aa17e54b626fe2
|
||||
Reference in new issue
Block a user