|
|
"""SQLAlchemy models for Project Memory.""" |
|
|
|
|
|
from sqlalchemy import Column, String, DateTime, ForeignKey, Text, Enum, JSON, Boolean |
|
|
from sqlalchemy.orm import relationship |
|
|
from datetime import datetime |
|
|
import uuid |
|
|
import enum |
|
|
import random |
|
|
|
|
|
from app.database import Base |
|
|
|
|
|
|
|
|
|
|
|
AI_AGENT_USER_ID = "ai-agent" |
|
|
|
|
|
|
|
|
def generate_uuid() -> str: |
|
|
"""Generate a new UUID string.""" |
|
|
return str(uuid.uuid4()) |
|
|
|
|
|
|
|
|
def generate_user_id(first_name: str) -> str: |
|
|
"""Generate user ID: first 3 letters of firstname + 4 random digits. |
|
|
|
|
|
Example: 'Amal' -> 'ama1234' |
|
|
""" |
|
|
prefix = first_name[:3].lower() |
|
|
suffix = ''.join([str(random.randint(0, 9)) for _ in range(4)]) |
|
|
return f"{prefix}{suffix}" |
|
|
|
|
|
|
|
|
class ActorType(str, enum.Enum): |
|
|
"""Who performed the action.""" |
|
|
human = "human" |
|
|
agent = "agent" |
|
|
|
|
|
|
|
|
class ActionType(str, enum.Enum): |
|
|
"""Type of action recorded.""" |
|
|
task_completed = "task_completed" |
|
|
doc_generated = "doc_generated" |
|
|
query_answered = "query_answered" |
|
|
|
|
|
|
|
|
class TaskStatus(str, enum.Enum): |
|
|
"""Task status states.""" |
|
|
todo = "todo" |
|
|
in_progress = "in_progress" |
|
|
done = "done" |
|
|
|
|
|
|
|
|
class User(Base): |
|
|
"""User account.""" |
|
|
__tablename__ = "users" |
|
|
|
|
|
id = Column(String, primary_key=True) |
|
|
first_name = Column(String, nullable=False) |
|
|
last_name = Column(String, nullable=False) |
|
|
avatar_url = Column(String, nullable=True) |
|
|
created_at = Column(DateTime, default=datetime.utcnow) |
|
|
|
|
|
|
|
|
memberships = relationship("ProjectMembership", back_populates="user") |
|
|
created_projects = relationship("Project", back_populates="creator") |
|
|
log_entries = relationship("LogEntry", back_populates="user") |
|
|
|
|
|
@property |
|
|
def name(self) -> str: |
|
|
"""Full name for backward compatibility.""" |
|
|
return f"{self.first_name} {self.last_name}" |
|
|
|
|
|
|
|
|
class Project(Base): |
|
|
"""Project that contains tasks and memory. |
|
|
|
|
|
Option A: project name is also the stable project ID. We persist the ID explicitly |
|
|
from the tools layer (create_project uses the provided name as id). |
|
|
""" |
|
|
__tablename__ = "projects" |
|
|
|
|
|
|
|
|
id = Column(String, primary_key=True) |
|
|
name = Column(String, nullable=False) |
|
|
description = Column(Text, nullable=True) |
|
|
created_at = Column(DateTime, default=datetime.utcnow) |
|
|
created_by = Column(String, ForeignKey("users.id"), nullable=True) |
|
|
agent_enabled = Column(Boolean, default=False) |
|
|
|
|
|
|
|
|
creator = relationship("User", back_populates="created_projects") |
|
|
memberships = relationship("ProjectMembership", back_populates="project") |
|
|
tasks = relationship("Task", back_populates="project") |
|
|
log_entries = relationship("LogEntry", back_populates="project") |
|
|
|
|
|
|
|
|
class ProjectMembership(Base): |
|
|
"""Association between users and projects.""" |
|
|
__tablename__ = "project_memberships" |
|
|
|
|
|
id = Column(String, primary_key=True, default=generate_uuid) |
|
|
project_id = Column(String, ForeignKey("projects.id"), nullable=False) |
|
|
user_id = Column(String, ForeignKey("users.id"), nullable=False) |
|
|
role = Column(String, default="member") |
|
|
joined_at = Column(DateTime, default=datetime.utcnow) |
|
|
|
|
|
|
|
|
project = relationship("Project", back_populates="memberships") |
|
|
user = relationship("User", back_populates="memberships") |
|
|
|
|
|
|
|
|
class Task(Base): |
|
|
"""Task within a project.""" |
|
|
__tablename__ = "tasks" |
|
|
|
|
|
id = Column(String, primary_key=True, default=generate_uuid) |
|
|
project_id = Column(String, ForeignKey("projects.id"), nullable=False) |
|
|
title = Column(String, nullable=False) |
|
|
description = Column(Text, nullable=True) |
|
|
status = Column(Enum(TaskStatus), default=TaskStatus.todo) |
|
|
assigned_to = Column(String, nullable=True) |
|
|
working_by = Column(String, nullable=True) |
|
|
created_at = Column(DateTime, default=datetime.utcnow) |
|
|
completed_at = Column(DateTime, nullable=True) |
|
|
|
|
|
|
|
|
project = relationship("Project", back_populates="tasks") |
|
|
log_entries = relationship("LogEntry", back_populates="task") |
|
|
|
|
|
|
|
|
class LogEntry(Base): |
|
|
""" |
|
|
The core of project memory. |
|
|
Records what was done, by whom, and stores LLM-generated documentation. |
|
|
""" |
|
|
__tablename__ = "log_entries" |
|
|
|
|
|
id = Column(String, primary_key=True, default=generate_uuid) |
|
|
project_id = Column(String, ForeignKey("projects.id"), nullable=False) |
|
|
task_id = Column(String, ForeignKey("tasks.id"), nullable=True) |
|
|
user_id = Column(String, ForeignKey("users.id"), nullable=True) |
|
|
actor_type = Column(Enum(ActorType), nullable=False) |
|
|
action_type = Column(Enum(ActionType), nullable=False) |
|
|
raw_input = Column(Text, nullable=False) |
|
|
code_snippet = Column(Text, nullable=True) |
|
|
generated_doc = Column(Text, nullable=False) |
|
|
tags = Column(JSON, default=list) |
|
|
created_at = Column(DateTime, default=datetime.utcnow) |
|
|
|
|
|
|
|
|
project = relationship("Project", back_populates="log_entries") |
|
|
task = relationship("Task", back_populates="log_entries") |
|
|
user = relationship("User", back_populates="log_entries") |
|
|
|