File size: 5,041 Bytes
c4d486b
 
 
 
 
 
 
 
 
 
 
 
 
e5b256c
c4d486b
 
 
 
 
 
 
3b6e5dc
 
 
c4d486b
 
 
e5b256c
 
 
 
 
 
 
 
 
c4d486b
3b6e5dc
 
 
 
 
 
 
 
 
c4d486b
 
 
 
 
 
 
 
 
 
e5b256c
c4d486b
 
 
 
 
 
 
 
 
 
 
e5b256c
c4d486b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
e5b256c
c4d486b
 
 
 
 
 
 
 
 
e5b256c
c4d486b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3b6e5dc
 
 
 
c4d486b
 
3b6e5dc
 
 
 
 
c4d486b
3b6e5dc
 
 
 
 
 
a066e5a
 
 
3b6e5dc
a066e5a
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
# app/models/database.py
from datetime import datetime, timezone
import uuid
from sqlalchemy import (
    create_engine,
    Column,
    Integer,
    String,
    Text,
    DateTime,
    Float,
    Boolean,
    ForeignKey,
    MetaData,
)

# from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker, relationship, declarative_base
from sqlalchemy.dialects.postgresql import UUID

# Absolute import from project root
from app.utils.config import get_logger, config_manager

config = config_manager.get_config()

logger = get_logger(__name__)

convention = {
    "ix": "ix_%(column_0_label)s",
    "uq": "uq_%(table_name)s_%(column_0_name)s",
    "ck": "ck_%(table_name)s_%(constraint_name)s",
    "fk": "fk_%(table_name)s_%(column_0_name)s_%(referred_table_name)s",
    "pk": "pk_%(table_name)s",
}

Base = declarative_base(metadata=MetaData(naming_convention=convention))

engine = create_engine(
    config.database_url,
    echo=False,  # Set to True for SQL debugging
    pool_pre_ping=True,  # Verify connections before use
    pool_recycle=300,  # Recycle connections every 5 minutes
)

SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)


class User(Base):
    __tablename__ = "users"

    id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
    session_id = Column(String(255), unique=True, index=True)
    preferred_language = Column(String(10), default="en")
    first_seen = Column(DateTime, default=lambda: datetime.now(timezone.utc))
    last_seen = Column(DateTime, default=lambda: datetime.now(timezone.utc))
    total_messages = Column(Integer, default=0)
    tenant_id = Column(UUID(as_uuid=True), ForeignKey("tenants.id"), nullable=True)

    # Relationships
    conversations = relationship(
        "Conversation", back_populates="user", cascade="all, delete-orphan"
    )
    preferences = relationship(
        "UserPreference", back_populates="user", cascade="all, delete-orphan"
    )
    insights = relationship(
        "UserInsight", back_populates="user", cascade="all, delete-orphan"
    )
    tenant = relationship("Tenant", back_populates="users")

    def __repr__(self):
        return (
            f"<User(session_id={self.session_id}, language={self.preferred_language})>"
        )


class Conversation(Base):
    __tablename__ = "conversations"

    id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
    user_id = Column(UUID(as_uuid=True), ForeignKey("users.id"), nullable=False)
    started_at = Column(DateTime, default=lambda: datetime.now(timezone.utc))
    last_message_at = Column(DateTime, default=lambda: datetime.now(timezone.utc))
    message_count = Column(Integer, default=0)
    is_active = Column(Boolean, default=True)
    tenant_id = Column(UUID(as_uuid=True), ForeignKey("tenants.id"), nullable=True)

    # Relationship
    user = relationship("User", back_populates="conversations")
    messages = relationship(
        "Message", back_populates="conversation", cascade="all, delete-orphan"
    )
    topics = relationship(
        "ConversationTopic", back_populates="conversation", cascade="all, delete-orphan"
    )
    tenant = relationship("Tenant", back_populates="conversations")

    def __repr__(self):
        return f"<Conversation(id={self.id}, user_id={self.user_id}, messages={self.message_count})>"


class Message(Base):
    __tablename__ = "messages"

    id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
    conversation_id = Column(
        UUID(as_uuid=True), ForeignKey("conversations.id"), nullable=False
    )

    # Message content
    user_input = Column(Text, nullable=False)
    bot_response = Column(Text, nullable=False)

    # NLP analysis
    detected_language = Column(String(10))
    intent = Column(String(100))
    confidence = Column(Float)
    entities = Column(Text)  # JSON string

    # Metadata
    timestamp = Column(DateTime, default=lambda: datetime.now(timezone.utc))
    response_time_ms = Column(Integer)  # How long the bot took to respond

    # Relationships
    conversation = relationship("Conversation", back_populates="messages")

    def __repr__(self):
        return f"<Message(id={self.id}, intent={self.intent}, confidence={self.confidence})>"


def init_database():
    # create all tables in the database
    Base.metadata.create_all(bind=engine)
    logger.info("Database tables created successfully")


def health_check():
    """Check if database connection is working"""
    try:
        with SessionLocal() as session:
            from sqlalchemy import text  # pylint: disable=import-outside-toplevel

            session.execute(text("SELECT 1"))
        logger.info("Database health check passed")
        return True
    except Exception as e:  # pylint: disable=broad-exception-caught
        logger.error("Database health check failed: %s: %s", type(e).__name__, str(e))
        return False


def get_db():
    db = SessionLocal()
    try:
        yield db
    finally:
        db.close()