kairo-code 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (144) hide show
  1. image-service/main.py +178 -0
  2. infra/chat/app/main.py +84 -0
  3. kairo/backend/__init__.py +0 -0
  4. kairo/backend/api/__init__.py +0 -0
  5. kairo/backend/api/admin/__init__.py +23 -0
  6. kairo/backend/api/admin/audit.py +54 -0
  7. kairo/backend/api/admin/content.py +142 -0
  8. kairo/backend/api/admin/incidents.py +148 -0
  9. kairo/backend/api/admin/stats.py +125 -0
  10. kairo/backend/api/admin/system.py +87 -0
  11. kairo/backend/api/admin/users.py +279 -0
  12. kairo/backend/api/agents.py +94 -0
  13. kairo/backend/api/api_keys.py +85 -0
  14. kairo/backend/api/auth.py +116 -0
  15. kairo/backend/api/billing.py +41 -0
  16. kairo/backend/api/chat.py +72 -0
  17. kairo/backend/api/conversations.py +125 -0
  18. kairo/backend/api/device_auth.py +100 -0
  19. kairo/backend/api/files.py +83 -0
  20. kairo/backend/api/health.py +36 -0
  21. kairo/backend/api/images.py +80 -0
  22. kairo/backend/api/openai_compat.py +225 -0
  23. kairo/backend/api/projects.py +102 -0
  24. kairo/backend/api/usage.py +32 -0
  25. kairo/backend/api/webhooks.py +79 -0
  26. kairo/backend/app.py +297 -0
  27. kairo/backend/config.py +179 -0
  28. kairo/backend/core/__init__.py +0 -0
  29. kairo/backend/core/admin_auth.py +24 -0
  30. kairo/backend/core/api_key_auth.py +55 -0
  31. kairo/backend/core/database.py +28 -0
  32. kairo/backend/core/dependencies.py +70 -0
  33. kairo/backend/core/logging.py +23 -0
  34. kairo/backend/core/rate_limit.py +73 -0
  35. kairo/backend/core/security.py +29 -0
  36. kairo/backend/models/__init__.py +19 -0
  37. kairo/backend/models/agent.py +30 -0
  38. kairo/backend/models/api_key.py +25 -0
  39. kairo/backend/models/api_usage.py +29 -0
  40. kairo/backend/models/audit_log.py +26 -0
  41. kairo/backend/models/conversation.py +48 -0
  42. kairo/backend/models/device_code.py +30 -0
  43. kairo/backend/models/feature_flag.py +21 -0
  44. kairo/backend/models/image_generation.py +24 -0
  45. kairo/backend/models/incident.py +28 -0
  46. kairo/backend/models/project.py +28 -0
  47. kairo/backend/models/uptime_record.py +24 -0
  48. kairo/backend/models/usage.py +24 -0
  49. kairo/backend/models/user.py +49 -0
  50. kairo/backend/schemas/__init__.py +0 -0
  51. kairo/backend/schemas/admin/__init__.py +0 -0
  52. kairo/backend/schemas/admin/audit.py +28 -0
  53. kairo/backend/schemas/admin/content.py +53 -0
  54. kairo/backend/schemas/admin/stats.py +77 -0
  55. kairo/backend/schemas/admin/system.py +44 -0
  56. kairo/backend/schemas/admin/users.py +48 -0
  57. kairo/backend/schemas/agent.py +42 -0
  58. kairo/backend/schemas/api_key.py +30 -0
  59. kairo/backend/schemas/auth.py +57 -0
  60. kairo/backend/schemas/chat.py +26 -0
  61. kairo/backend/schemas/conversation.py +39 -0
  62. kairo/backend/schemas/device_auth.py +40 -0
  63. kairo/backend/schemas/image.py +15 -0
  64. kairo/backend/schemas/openai_compat.py +76 -0
  65. kairo/backend/schemas/project.py +21 -0
  66. kairo/backend/schemas/status.py +81 -0
  67. kairo/backend/schemas/usage.py +15 -0
  68. kairo/backend/services/__init__.py +0 -0
  69. kairo/backend/services/admin/__init__.py +0 -0
  70. kairo/backend/services/admin/audit_service.py +78 -0
  71. kairo/backend/services/admin/content_service.py +119 -0
  72. kairo/backend/services/admin/incident_service.py +94 -0
  73. kairo/backend/services/admin/stats_service.py +281 -0
  74. kairo/backend/services/admin/system_service.py +126 -0
  75. kairo/backend/services/admin/user_service.py +157 -0
  76. kairo/backend/services/agent_service.py +107 -0
  77. kairo/backend/services/api_key_service.py +66 -0
  78. kairo/backend/services/api_usage_service.py +126 -0
  79. kairo/backend/services/auth_service.py +101 -0
  80. kairo/backend/services/chat_service.py +501 -0
  81. kairo/backend/services/conversation_service.py +264 -0
  82. kairo/backend/services/device_auth_service.py +193 -0
  83. kairo/backend/services/email_service.py +55 -0
  84. kairo/backend/services/image_service.py +181 -0
  85. kairo/backend/services/llm_service.py +186 -0
  86. kairo/backend/services/project_service.py +109 -0
  87. kairo/backend/services/status_service.py +167 -0
  88. kairo/backend/services/stripe_service.py +78 -0
  89. kairo/backend/services/usage_service.py +150 -0
  90. kairo/backend/services/web_search_service.py +96 -0
  91. kairo/migrations/env.py +60 -0
  92. kairo/migrations/versions/001_initial.py +55 -0
  93. kairo/migrations/versions/002_usage_tracking_and_indexes.py +66 -0
  94. kairo/migrations/versions/003_username_to_email.py +21 -0
  95. kairo/migrations/versions/004_add_plans_and_verification.py +67 -0
  96. kairo/migrations/versions/005_add_projects.py +52 -0
  97. kairo/migrations/versions/006_add_image_generation.py +63 -0
  98. kairo/migrations/versions/007_add_admin_portal.py +107 -0
  99. kairo/migrations/versions/008_add_device_code_auth.py +76 -0
  100. kairo/migrations/versions/009_add_status_page.py +65 -0
  101. kairo/tools/extract_claude_data.py +465 -0
  102. kairo/tools/filter_claude_data.py +303 -0
  103. kairo/tools/generate_curated_data.py +157 -0
  104. kairo/tools/mix_training_data.py +295 -0
  105. kairo_code/__init__.py +3 -0
  106. kairo_code/agents/__init__.py +25 -0
  107. kairo_code/agents/architect.py +98 -0
  108. kairo_code/agents/audit.py +100 -0
  109. kairo_code/agents/base.py +463 -0
  110. kairo_code/agents/coder.py +155 -0
  111. kairo_code/agents/database.py +77 -0
  112. kairo_code/agents/docs.py +88 -0
  113. kairo_code/agents/explorer.py +62 -0
  114. kairo_code/agents/guardian.py +80 -0
  115. kairo_code/agents/planner.py +66 -0
  116. kairo_code/agents/reviewer.py +91 -0
  117. kairo_code/agents/security.py +94 -0
  118. kairo_code/agents/terraform.py +88 -0
  119. kairo_code/agents/testing.py +97 -0
  120. kairo_code/agents/uiux.py +88 -0
  121. kairo_code/auth.py +232 -0
  122. kairo_code/config.py +172 -0
  123. kairo_code/conversation.py +173 -0
  124. kairo_code/heartbeat.py +63 -0
  125. kairo_code/llm.py +291 -0
  126. kairo_code/logging_config.py +156 -0
  127. kairo_code/main.py +818 -0
  128. kairo_code/router.py +217 -0
  129. kairo_code/sandbox.py +248 -0
  130. kairo_code/settings.py +183 -0
  131. kairo_code/tools/__init__.py +51 -0
  132. kairo_code/tools/analysis.py +509 -0
  133. kairo_code/tools/base.py +417 -0
  134. kairo_code/tools/code.py +58 -0
  135. kairo_code/tools/definitions.py +617 -0
  136. kairo_code/tools/files.py +315 -0
  137. kairo_code/tools/review.py +390 -0
  138. kairo_code/tools/search.py +185 -0
  139. kairo_code/ui.py +418 -0
  140. kairo_code-0.1.0.dist-info/METADATA +13 -0
  141. kairo_code-0.1.0.dist-info/RECORD +144 -0
  142. kairo_code-0.1.0.dist-info/WHEEL +5 -0
  143. kairo_code-0.1.0.dist-info/entry_points.txt +2 -0
  144. kairo_code-0.1.0.dist-info/top_level.txt +4 -0
@@ -0,0 +1,73 @@
1
+ import time
2
+ from collections import defaultdict
3
+
4
+ from fastapi import HTTPException, Request
5
+
6
+ # IPs of known reverse proxies (e.g., nginx on localhost, Docker bridge)
7
+ _TRUSTED_PROXIES = {"127.0.0.1", "::1", "172.17.0.1", "172.18.0.1"}
8
+
9
+
10
+ class SlidingWindowRateLimiter:
11
+ """In-memory sliding window rate limiter."""
12
+
13
+ def __init__(self, max_requests: int, window_seconds: int):
14
+ self.max_requests = max_requests
15
+ self.window_seconds = window_seconds
16
+ self._requests: dict[str, list[float]] = defaultdict(list)
17
+
18
+ def _clean(self, key: str, now: float) -> None:
19
+ cutoff = now - self.window_seconds
20
+ self._requests[key] = [t for t in self._requests[key] if t > cutoff]
21
+ if not self._requests[key]:
22
+ del self._requests[key]
23
+
24
+ def check(self, key: str) -> bool:
25
+ now = time.monotonic()
26
+ self._clean(key, now)
27
+ if len(self._requests.get(key, [])) >= self.max_requests:
28
+ return False
29
+ self._requests[key].append(now)
30
+ return True
31
+
32
+
33
+ # 10 requests/minute per IP for auth endpoints
34
+ _auth_limiter = SlidingWindowRateLimiter(max_requests=10, window_seconds=60)
35
+
36
+ # 30 requests/minute per IP for chat endpoints
37
+ _chat_limiter = SlidingWindowRateLimiter(max_requests=30, window_seconds=60)
38
+
39
+
40
+ def _client_ip(request: Request) -> str:
41
+ """Get client IP, only trusting X-Forwarded-For from known proxies."""
42
+ real_ip = request.client.host if request.client else "unknown"
43
+ if real_ip in _TRUSTED_PROXIES:
44
+ forwarded = request.headers.get("x-forwarded-for")
45
+ if forwarded:
46
+ return forwarded.split(",")[0].strip()
47
+ return real_ip
48
+
49
+
50
+ async def rate_limit_auth(request: Request) -> None:
51
+ key = f"auth:{_client_ip(request)}"
52
+ if not _auth_limiter.check(key):
53
+ raise HTTPException(status_code=429, detail="Too many requests. Try again later.")
54
+
55
+
56
+ async def rate_limit_chat(request: Request) -> None:
57
+ key = f"chat:{_client_ip(request)}"
58
+ if not _chat_limiter.check(key):
59
+ raise HTTPException(status_code=429, detail="Too many requests. Try again later.")
60
+
61
+
62
+ # 60 requests/minute per API key
63
+ _api_limiter = SlidingWindowRateLimiter(max_requests=60, window_seconds=60)
64
+
65
+
66
+ async def rate_limit_api(request: Request) -> None:
67
+ auth = request.headers.get("Authorization", "")
68
+ if auth.startswith("Bearer sk-kairo-"):
69
+ key = f"api:{auth[7:27]}" # use key prefix
70
+ else:
71
+ key = f"api:{_client_ip(request)}"
72
+ if not _api_limiter.check(key):
73
+ raise HTTPException(status_code=429, detail="API rate limit exceeded.")
@@ -0,0 +1,29 @@
1
+ from datetime import datetime, timedelta, timezone
2
+
3
+ import bcrypt
4
+ from jose import JWTError, jwt
5
+
6
+ from backend.config import settings
7
+
8
+
9
+ def hash_password(password: str) -> str:
10
+ return bcrypt.hashpw(password.encode("utf-8"), bcrypt.gensalt()).decode("utf-8")
11
+
12
+
13
+ def verify_password(plain: str, hashed: str) -> bool:
14
+ return bcrypt.checkpw(plain.encode("utf-8"), hashed.encode("utf-8"))
15
+
16
+
17
+ def create_access_token(user_id: str) -> str:
18
+ expire = datetime.now(timezone.utc) + timedelta(hours=settings.JWT_EXPIRE_HOURS)
19
+ payload = {"sub": user_id, "exp": expire}
20
+ return jwt.encode(payload, settings.JWT_SECRET_KEY, algorithm="HS256")
21
+
22
+
23
+ def decode_token(token: str) -> str | None:
24
+ """Returns user_id if valid, None otherwise."""
25
+ try:
26
+ payload = jwt.decode(token, settings.JWT_SECRET_KEY, algorithms=["HS256"])
27
+ return payload.get("sub")
28
+ except JWTError:
29
+ return None
@@ -0,0 +1,19 @@
1
+ from backend.models.conversation import Conversation, Message
2
+ from backend.models.project import Project
3
+ from backend.models.user import User, PlanType, RoleType
4
+ from backend.models.usage import UsageRecord
5
+ from backend.models.api_key import ApiKey
6
+ from backend.models.agent import Agent
7
+ from backend.models.api_usage import ApiUsageRecord
8
+ from backend.models.image_generation import ImageGeneration
9
+ from backend.models.audit_log import AuditLog
10
+ from backend.models.feature_flag import FeatureFlag
11
+ from backend.models.device_code import DeviceCode
12
+ from backend.models.incident import Incident
13
+ from backend.models.uptime_record import UptimeRecord
14
+
15
+ __all__ = [
16
+ "Conversation", "Message", "Project", "User", "PlanType", "RoleType",
17
+ "UsageRecord", "ApiKey", "Agent", "ApiUsageRecord", "ImageGeneration",
18
+ "AuditLog", "FeatureFlag", "DeviceCode", "Incident", "UptimeRecord",
19
+ ]
@@ -0,0 +1,30 @@
1
+ import uuid
2
+ from datetime import datetime, UTC
3
+
4
+ from sqlalchemy import DateTime, ForeignKey, String, Text
5
+ from sqlalchemy.orm import Mapped, mapped_column
6
+
7
+ from backend.core.database import Base
8
+
9
+
10
+ class Agent(Base):
11
+ __tablename__ = "agents"
12
+
13
+ id: Mapped[str] = mapped_column(primary_key=True, default=lambda: str(uuid.uuid4()))
14
+ user_id: Mapped[str] = mapped_column(ForeignKey("users.id", ondelete="CASCADE"), index=True)
15
+ api_key_id: Mapped[str | None] = mapped_column(
16
+ ForeignKey("api_keys.id", ondelete="SET NULL"), nullable=True
17
+ )
18
+ name: Mapped[str] = mapped_column(String)
19
+ description: Mapped[str | None] = mapped_column(Text, nullable=True)
20
+ system_prompt: Mapped[str | None] = mapped_column(Text, nullable=True)
21
+ model_preference: Mapped[str] = mapped_column(String, default="nyx")
22
+ tools_config: Mapped[str | None] = mapped_column(Text, nullable=True)
23
+ status: Mapped[str] = mapped_column(String, default="offline")
24
+ last_heartbeat_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
25
+ created_at: Mapped[datetime] = mapped_column(
26
+ DateTime(timezone=True), default=lambda: datetime.now(UTC)
27
+ )
28
+ updated_at: Mapped[datetime] = mapped_column(
29
+ DateTime(timezone=True), default=lambda: datetime.now(UTC), onupdate=lambda: datetime.now(UTC)
30
+ )
@@ -0,0 +1,25 @@
1
+ import uuid
2
+ from datetime import datetime, UTC
3
+
4
+ from sqlalchemy import Boolean, DateTime, ForeignKey, Integer, String
5
+ from sqlalchemy.orm import Mapped, mapped_column
6
+
7
+ from backend.core.database import Base
8
+
9
+
10
+ class ApiKey(Base):
11
+ __tablename__ = "api_keys"
12
+
13
+ id: Mapped[str] = mapped_column(primary_key=True, default=lambda: str(uuid.uuid4()))
14
+ user_id: Mapped[str] = mapped_column(ForeignKey("users.id", ondelete="CASCADE"), index=True)
15
+ name: Mapped[str] = mapped_column(String)
16
+ key_prefix: Mapped[str] = mapped_column(String, index=True)
17
+ key_hash: Mapped[str] = mapped_column(String)
18
+ is_active: Mapped[bool] = mapped_column(Boolean, default=True)
19
+ last_used_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
20
+ created_at: Mapped[datetime] = mapped_column(
21
+ DateTime(timezone=True), default=lambda: datetime.now(UTC)
22
+ )
23
+ expires_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
24
+ key_type: Mapped[str] = mapped_column(String, default="api")
25
+ rate_limit_rpm: Mapped[int] = mapped_column(Integer, default=60)
@@ -0,0 +1,29 @@
1
+ import uuid
2
+ from datetime import datetime, UTC
3
+
4
+ from sqlalchemy import DateTime, ForeignKey, Integer, String
5
+ from sqlalchemy.orm import Mapped, mapped_column
6
+
7
+ from backend.core.database import Base
8
+
9
+
10
+ class ApiUsageRecord(Base):
11
+ __tablename__ = "api_usage_records"
12
+
13
+ id: Mapped[str] = mapped_column(primary_key=True, default=lambda: str(uuid.uuid4()))
14
+ api_key_id: Mapped[str] = mapped_column(
15
+ ForeignKey("api_keys.id", ondelete="CASCADE"), index=True
16
+ )
17
+ user_id: Mapped[str] = mapped_column(
18
+ ForeignKey("users.id", ondelete="CASCADE"), index=True
19
+ )
20
+ agent_id: Mapped[str | None] = mapped_column(
21
+ ForeignKey("agents.id", ondelete="SET NULL"), nullable=True
22
+ )
23
+ model: Mapped[str] = mapped_column(String)
24
+ prompt_tokens: Mapped[int] = mapped_column(Integer)
25
+ completion_tokens: Mapped[int] = mapped_column(Integer)
26
+ created_at: Mapped[datetime] = mapped_column(
27
+ DateTime(timezone=True), default=lambda: datetime.now(UTC)
28
+ )
29
+ endpoint: Mapped[str] = mapped_column(String)
@@ -0,0 +1,26 @@
1
+ import uuid
2
+ from datetime import datetime, UTC
3
+
4
+ from sqlalchemy import DateTime, ForeignKey, String, Text
5
+ from sqlalchemy.dialects.postgresql import JSON
6
+ from sqlalchemy.orm import Mapped, mapped_column
7
+
8
+ from backend.core.database import Base
9
+
10
+
11
+ class AuditLog(Base):
12
+ __tablename__ = "audit_logs"
13
+
14
+ id: Mapped[str] = mapped_column(primary_key=True, default=lambda: str(uuid.uuid4()))
15
+ admin_user_id: Mapped[str] = mapped_column(ForeignKey("users.id", ondelete="CASCADE"))
16
+ action: Mapped[str] = mapped_column(String)
17
+ target_type: Mapped[str] = mapped_column(String)
18
+ target_id: Mapped[str | None] = mapped_column(String, nullable=True)
19
+ details: Mapped[dict | None] = mapped_column(JSON, nullable=True)
20
+ result: Mapped[str] = mapped_column(String, default="success")
21
+ ip_address: Mapped[str | None] = mapped_column(String, nullable=True)
22
+ user_agent: Mapped[str | None] = mapped_column(Text, nullable=True)
23
+ session_id: Mapped[str | None] = mapped_column(String, nullable=True)
24
+ created_at: Mapped[datetime] = mapped_column(
25
+ DateTime(timezone=True), default=lambda: datetime.now(UTC)
26
+ )
@@ -0,0 +1,48 @@
1
+ import uuid
2
+ from datetime import datetime, UTC
3
+
4
+ from sqlalchemy import DateTime, ForeignKey, String, Text
5
+ from sqlalchemy.orm import Mapped, mapped_column, relationship
6
+
7
+ from backend.core.database import Base
8
+
9
+
10
+ class Conversation(Base):
11
+ __tablename__ = "conversations"
12
+
13
+ id: Mapped[str] = mapped_column(primary_key=True, default=lambda: str(uuid.uuid4()))
14
+ title: Mapped[str] = mapped_column(default="New Conversation")
15
+ model: Mapped[str] = mapped_column(default="nyx")
16
+ user_id: Mapped[str | None] = mapped_column(ForeignKey("users.id"), nullable=True)
17
+ created_at: Mapped[datetime] = mapped_column(
18
+ DateTime(timezone=True), default=lambda: datetime.now(UTC)
19
+ )
20
+ summary: Mapped[str | None] = mapped_column(Text, nullable=True, default=None)
21
+ updated_at: Mapped[datetime] = mapped_column(
22
+ DateTime(timezone=True),
23
+ default=lambda: datetime.now(UTC),
24
+ onupdate=lambda: datetime.now(UTC),
25
+ )
26
+ project_id: Mapped[str | None] = mapped_column(
27
+ ForeignKey("projects.id", ondelete="SET NULL"), nullable=True, default=None
28
+ )
29
+
30
+ messages: Mapped[list["Message"]] = relationship(
31
+ back_populates="conversation", cascade="all, delete-orphan", order_by="Message.created_at"
32
+ )
33
+ project: Mapped["Project | None"] = relationship(back_populates="conversations") # noqa: F821
34
+
35
+
36
+ class Message(Base):
37
+ __tablename__ = "messages"
38
+
39
+ id: Mapped[str] = mapped_column(primary_key=True, default=lambda: str(uuid.uuid4()))
40
+ conversation_id: Mapped[str] = mapped_column(ForeignKey("conversations.id", ondelete="CASCADE"))
41
+ role: Mapped[str] = mapped_column() # "user", "assistant", "system"
42
+ content: Mapped[str] = mapped_column(Text)
43
+ image_url: Mapped[str | None] = mapped_column(String, nullable=True, default=None)
44
+ created_at: Mapped[datetime] = mapped_column(
45
+ DateTime(timezone=True), default=lambda: datetime.now(UTC)
46
+ )
47
+
48
+ conversation: Mapped["Conversation"] = relationship(back_populates="messages")
@@ -0,0 +1,30 @@
1
+ import uuid
2
+ from datetime import datetime, UTC
3
+
4
+ from sqlalchemy import DateTime, ForeignKey, Integer, String
5
+ from sqlalchemy.orm import Mapped, mapped_column
6
+
7
+ from backend.core.database import Base
8
+
9
+
10
+ class DeviceCode(Base):
11
+ __tablename__ = "device_codes"
12
+
13
+ id: Mapped[str] = mapped_column(primary_key=True, default=lambda: str(uuid.uuid4()))
14
+ device_code: Mapped[str] = mapped_column(String, unique=True, index=True)
15
+ user_code: Mapped[str] = mapped_column(String, unique=True, index=True)
16
+ user_id: Mapped[str | None] = mapped_column(
17
+ ForeignKey("users.id", ondelete="CASCADE"), nullable=True
18
+ )
19
+ status: Mapped[str] = mapped_column(String, default="pending")
20
+ cli_api_key_id: Mapped[str | None] = mapped_column(
21
+ ForeignKey("api_keys.id", ondelete="SET NULL"), nullable=True
22
+ )
23
+ expires_at: Mapped[datetime] = mapped_column(DateTime(timezone=True))
24
+ created_at: Mapped[datetime] = mapped_column(
25
+ DateTime(timezone=True), default=lambda: datetime.now(UTC)
26
+ )
27
+ approved_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
28
+ client_name: Mapped[str | None] = mapped_column(String, nullable=True)
29
+ cli_token: Mapped[str | None] = mapped_column(String, nullable=True)
30
+ interval: Mapped[int] = mapped_column(Integer, default=5)
@@ -0,0 +1,21 @@
1
+ import uuid
2
+ from datetime import datetime, UTC
3
+
4
+ from sqlalchemy import Boolean, DateTime, ForeignKey, String
5
+ from sqlalchemy.orm import Mapped, mapped_column
6
+
7
+ from backend.core.database import Base
8
+
9
+
10
+ class FeatureFlag(Base):
11
+ __tablename__ = "feature_flags"
12
+
13
+ id: Mapped[str] = mapped_column(primary_key=True, default=lambda: str(uuid.uuid4()))
14
+ key: Mapped[str] = mapped_column(String, unique=True)
15
+ enabled: Mapped[bool] = mapped_column(Boolean, default=False)
16
+ updated_by: Mapped[str | None] = mapped_column(
17
+ ForeignKey("users.id", ondelete="SET NULL"), nullable=True
18
+ )
19
+ updated_at: Mapped[datetime] = mapped_column(
20
+ DateTime(timezone=True), default=lambda: datetime.now(UTC)
21
+ )
@@ -0,0 +1,24 @@
1
+ import uuid
2
+ from datetime import datetime, UTC
3
+
4
+ from sqlalchemy import DateTime, ForeignKey, Integer, String
5
+ from sqlalchemy.orm import Mapped, mapped_column
6
+
7
+ from backend.core.database import Base
8
+
9
+
10
+ class ImageGeneration(Base):
11
+ __tablename__ = "image_generations"
12
+
13
+ id: Mapped[str] = mapped_column(primary_key=True, default=lambda: str(uuid.uuid4()))
14
+ user_id: Mapped[str] = mapped_column(ForeignKey("users.id", ondelete="CASCADE"))
15
+ conversation_id: Mapped[str | None] = mapped_column(
16
+ ForeignKey("conversations.id", ondelete="SET NULL"), nullable=True
17
+ )
18
+ prompt: Mapped[str] = mapped_column(String)
19
+ image_url: Mapped[str] = mapped_column(String)
20
+ width: Mapped[int] = mapped_column(Integer, default=1024)
21
+ height: Mapped[int] = mapped_column(Integer, default=1024)
22
+ created_at: Mapped[datetime] = mapped_column(
23
+ DateTime(timezone=True), default=lambda: datetime.now(UTC)
24
+ )
@@ -0,0 +1,28 @@
1
+ import uuid
2
+ from datetime import datetime, UTC
3
+
4
+ from sqlalchemy import DateTime, Index, String, Text
5
+ from sqlalchemy.orm import Mapped, mapped_column
6
+
7
+ from backend.core.database import Base
8
+
9
+
10
+ class Incident(Base):
11
+ __tablename__ = "incidents"
12
+
13
+ id: Mapped[str] = mapped_column(primary_key=True, default=lambda: str(uuid.uuid4()))
14
+ title: Mapped[str] = mapped_column(String)
15
+ description: Mapped[str | None] = mapped_column(Text, nullable=True)
16
+ severity: Mapped[str] = mapped_column(String, default="warning") # info, warning, critical
17
+ component: Mapped[str] = mapped_column(String, index=True) # api, chat, models, images, database
18
+ status: Mapped[str] = mapped_column(String, index=True, default="investigating") # investigating, identified, monitoring, resolved
19
+ started_at: Mapped[datetime] = mapped_column(
20
+ DateTime(timezone=True), index=True, default=lambda: datetime.now(UTC)
21
+ )
22
+ resolved_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
23
+ created_at: Mapped[datetime] = mapped_column(
24
+ DateTime(timezone=True), default=lambda: datetime.now(UTC)
25
+ )
26
+ updated_at: Mapped[datetime] = mapped_column(
27
+ DateTime(timezone=True), default=lambda: datetime.now(UTC), onupdate=lambda: datetime.now(UTC)
28
+ )
@@ -0,0 +1,28 @@
1
+ import uuid
2
+ from datetime import datetime, UTC
3
+
4
+ from sqlalchemy import DateTime, ForeignKey, Text
5
+ from sqlalchemy.orm import Mapped, mapped_column, relationship
6
+
7
+ from backend.core.database import Base
8
+
9
+
10
+ class Project(Base):
11
+ __tablename__ = "projects"
12
+
13
+ id: Mapped[str] = mapped_column(primary_key=True, default=lambda: str(uuid.uuid4()))
14
+ name: Mapped[str] = mapped_column()
15
+ instructions: Mapped[str | None] = mapped_column(Text, nullable=True, default=None)
16
+ user_id: Mapped[str] = mapped_column(ForeignKey("users.id"), nullable=False)
17
+ created_at: Mapped[datetime] = mapped_column(
18
+ DateTime(timezone=True), default=lambda: datetime.now(UTC)
19
+ )
20
+ updated_at: Mapped[datetime] = mapped_column(
21
+ DateTime(timezone=True),
22
+ default=lambda: datetime.now(UTC),
23
+ onupdate=lambda: datetime.now(UTC),
24
+ )
25
+
26
+ conversations: Mapped[list["Conversation"]] = relationship( # noqa: F821
27
+ back_populates="project", order_by="Conversation.updated_at.desc()"
28
+ )
@@ -0,0 +1,24 @@
1
+ import uuid
2
+ from datetime import date, datetime, UTC
3
+
4
+ from sqlalchemy import Date, DateTime, Float, Index, Integer, String
5
+ from sqlalchemy.orm import Mapped, mapped_column
6
+
7
+ from backend.core.database import Base
8
+
9
+
10
+ class UptimeRecord(Base):
11
+ __tablename__ = "uptime_records"
12
+ __table_args__ = (
13
+ Index("ix_uptime_records_component_date", "component", "date", unique=True),
14
+ )
15
+
16
+ id: Mapped[str] = mapped_column(primary_key=True, default=lambda: str(uuid.uuid4()))
17
+ component: Mapped[str] = mapped_column(String, index=True)
18
+ date: Mapped[date] = mapped_column(Date, index=True)
19
+ uptime_percent: Mapped[float] = mapped_column(Float, default=100.0)
20
+ incidents_count: Mapped[int] = mapped_column(Integer, default=0)
21
+ avg_latency_ms: Mapped[int | None] = mapped_column(Integer, nullable=True)
22
+ created_at: Mapped[datetime] = mapped_column(
23
+ DateTime(timezone=True), default=lambda: datetime.now(UTC)
24
+ )
@@ -0,0 +1,24 @@
1
+ import uuid
2
+ from datetime import datetime, UTC
3
+
4
+ from sqlalchemy import DateTime, ForeignKey, Integer
5
+ from sqlalchemy.orm import Mapped, mapped_column
6
+
7
+ from backend.core.database import Base
8
+
9
+
10
+ class UsageRecord(Base):
11
+ __tablename__ = "usage_records"
12
+
13
+ id: Mapped[str] = mapped_column(primary_key=True, default=lambda: str(uuid.uuid4()))
14
+ user_id: Mapped[str] = mapped_column(ForeignKey("users.id", ondelete="CASCADE"))
15
+ conversation_id: Mapped[str | None] = mapped_column(
16
+ ForeignKey("conversations.id", ondelete="SET NULL"), nullable=True
17
+ )
18
+ model: Mapped[str] = mapped_column()
19
+ prompt_tokens: Mapped[int] = mapped_column(Integer)
20
+ completion_tokens: Mapped[int] = mapped_column(Integer)
21
+ source: Mapped[str] = mapped_column(default="chat")
22
+ created_at: Mapped[datetime] = mapped_column(
23
+ DateTime(timezone=True), default=lambda: datetime.now(UTC)
24
+ )
@@ -0,0 +1,49 @@
1
+ import enum
2
+ import uuid
3
+ from datetime import datetime, timezone
4
+
5
+ from sqlalchemy import Boolean, DateTime, Integer, String
6
+ from sqlalchemy.orm import Mapped, mapped_column
7
+
8
+ from backend.core.database import Base
9
+
10
+
11
+ class PlanType(str, enum.Enum):
12
+ FREE = "free"
13
+ PRO = "pro"
14
+ MAX = "max"
15
+
16
+
17
+ class RoleType(str, enum.Enum):
18
+ USER = "user"
19
+ MODERATOR = "moderator"
20
+ ADMIN = "admin"
21
+ SUPERADMIN = "superadmin"
22
+
23
+
24
+ class User(Base):
25
+ __tablename__ = "users"
26
+
27
+ id: Mapped[str] = mapped_column(primary_key=True, default=lambda: str(uuid.uuid4()))
28
+ email: Mapped[str] = mapped_column(unique=True)
29
+ hashed_password: Mapped[str] = mapped_column()
30
+ created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=lambda: datetime.now(timezone.utc))
31
+ daily_token_limit: Mapped[int] = mapped_column(Integer, default=100_000)
32
+ monthly_token_limit: Mapped[int] = mapped_column(Integer, default=2_000_000)
33
+
34
+ # Plan & Stripe
35
+ plan: Mapped[str] = mapped_column(String, default=PlanType.FREE.value)
36
+ stripe_customer_id: Mapped[str | None] = mapped_column(String, nullable=True, unique=True, index=True)
37
+ stripe_subscription_id: Mapped[str | None] = mapped_column(String, nullable=True)
38
+
39
+ # Role & Status
40
+ role: Mapped[str] = mapped_column(String, default=RoleType.USER.value)
41
+ status: Mapped[str] = mapped_column(String, default="active")
42
+
43
+ # Email verification
44
+ email_verified: Mapped[bool] = mapped_column(Boolean, default=False)
45
+ email_verification_token: Mapped[str | None] = mapped_column(String, nullable=True)
46
+
47
+ # Password reset
48
+ password_reset_token: Mapped[str | None] = mapped_column(String, nullable=True)
49
+ password_reset_expires: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
File without changes
File without changes
@@ -0,0 +1,28 @@
1
+ from datetime import datetime
2
+
3
+ from pydantic import BaseModel
4
+
5
+
6
+ class AuditLogEntry(BaseModel):
7
+ id: str
8
+ admin_user_id: str
9
+ action: str
10
+ target_type: str
11
+ target_id: str | None = None
12
+ details: dict | None = None
13
+ result: str
14
+ ip_address: str | None = None
15
+ user_agent: str | None = None
16
+ session_id: str | None = None
17
+ created_at: datetime
18
+
19
+ model_config = {"from_attributes": True}
20
+
21
+
22
+ class AuditLogFilter(BaseModel):
23
+ admin_id: str | None = None
24
+ action: str | None = None
25
+ target_type: str | None = None
26
+ result: str | None = None
27
+ date_from: datetime | None = None
28
+ date_to: datetime | None = None
@@ -0,0 +1,53 @@
1
+ from datetime import datetime
2
+
3
+ from pydantic import BaseModel, Field
4
+
5
+
6
+ class AdminConversationListItem(BaseModel):
7
+ id: str
8
+ title: str
9
+ model: str
10
+ user_id: str | None = None
11
+ message_count: int = 0
12
+ created_at: datetime
13
+ updated_at: datetime
14
+
15
+ model_config = {"from_attributes": True}
16
+
17
+
18
+ class AdminMessageItem(BaseModel):
19
+ id: str
20
+ role: str
21
+ content: str
22
+ image_url: str | None = None
23
+ created_at: datetime
24
+
25
+ model_config = {"from_attributes": True}
26
+
27
+
28
+ class AdminConversationDetail(BaseModel):
29
+ id: str
30
+ title: str
31
+ model: str
32
+ user_id: str | None = None
33
+ created_at: datetime
34
+ updated_at: datetime
35
+ messages: list[AdminMessageItem] = []
36
+
37
+ model_config = {"from_attributes": True}
38
+
39
+
40
+ class AdminImageListItem(BaseModel):
41
+ id: str
42
+ user_id: str
43
+ prompt: str
44
+ image_url: str
45
+ width: int
46
+ height: int
47
+ created_at: datetime
48
+
49
+ model_config = {"from_attributes": True}
50
+
51
+
52
+ class ContentDeleteRequest(BaseModel):
53
+ reason: str = Field(..., min_length=1, max_length=1000)