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.
- image-service/main.py +178 -0
- infra/chat/app/main.py +84 -0
- kairo/backend/__init__.py +0 -0
- kairo/backend/api/__init__.py +0 -0
- kairo/backend/api/admin/__init__.py +23 -0
- kairo/backend/api/admin/audit.py +54 -0
- kairo/backend/api/admin/content.py +142 -0
- kairo/backend/api/admin/incidents.py +148 -0
- kairo/backend/api/admin/stats.py +125 -0
- kairo/backend/api/admin/system.py +87 -0
- kairo/backend/api/admin/users.py +279 -0
- kairo/backend/api/agents.py +94 -0
- kairo/backend/api/api_keys.py +85 -0
- kairo/backend/api/auth.py +116 -0
- kairo/backend/api/billing.py +41 -0
- kairo/backend/api/chat.py +72 -0
- kairo/backend/api/conversations.py +125 -0
- kairo/backend/api/device_auth.py +100 -0
- kairo/backend/api/files.py +83 -0
- kairo/backend/api/health.py +36 -0
- kairo/backend/api/images.py +80 -0
- kairo/backend/api/openai_compat.py +225 -0
- kairo/backend/api/projects.py +102 -0
- kairo/backend/api/usage.py +32 -0
- kairo/backend/api/webhooks.py +79 -0
- kairo/backend/app.py +297 -0
- kairo/backend/config.py +179 -0
- kairo/backend/core/__init__.py +0 -0
- kairo/backend/core/admin_auth.py +24 -0
- kairo/backend/core/api_key_auth.py +55 -0
- kairo/backend/core/database.py +28 -0
- kairo/backend/core/dependencies.py +70 -0
- kairo/backend/core/logging.py +23 -0
- kairo/backend/core/rate_limit.py +73 -0
- kairo/backend/core/security.py +29 -0
- kairo/backend/models/__init__.py +19 -0
- kairo/backend/models/agent.py +30 -0
- kairo/backend/models/api_key.py +25 -0
- kairo/backend/models/api_usage.py +29 -0
- kairo/backend/models/audit_log.py +26 -0
- kairo/backend/models/conversation.py +48 -0
- kairo/backend/models/device_code.py +30 -0
- kairo/backend/models/feature_flag.py +21 -0
- kairo/backend/models/image_generation.py +24 -0
- kairo/backend/models/incident.py +28 -0
- kairo/backend/models/project.py +28 -0
- kairo/backend/models/uptime_record.py +24 -0
- kairo/backend/models/usage.py +24 -0
- kairo/backend/models/user.py +49 -0
- kairo/backend/schemas/__init__.py +0 -0
- kairo/backend/schemas/admin/__init__.py +0 -0
- kairo/backend/schemas/admin/audit.py +28 -0
- kairo/backend/schemas/admin/content.py +53 -0
- kairo/backend/schemas/admin/stats.py +77 -0
- kairo/backend/schemas/admin/system.py +44 -0
- kairo/backend/schemas/admin/users.py +48 -0
- kairo/backend/schemas/agent.py +42 -0
- kairo/backend/schemas/api_key.py +30 -0
- kairo/backend/schemas/auth.py +57 -0
- kairo/backend/schemas/chat.py +26 -0
- kairo/backend/schemas/conversation.py +39 -0
- kairo/backend/schemas/device_auth.py +40 -0
- kairo/backend/schemas/image.py +15 -0
- kairo/backend/schemas/openai_compat.py +76 -0
- kairo/backend/schemas/project.py +21 -0
- kairo/backend/schemas/status.py +81 -0
- kairo/backend/schemas/usage.py +15 -0
- kairo/backend/services/__init__.py +0 -0
- kairo/backend/services/admin/__init__.py +0 -0
- kairo/backend/services/admin/audit_service.py +78 -0
- kairo/backend/services/admin/content_service.py +119 -0
- kairo/backend/services/admin/incident_service.py +94 -0
- kairo/backend/services/admin/stats_service.py +281 -0
- kairo/backend/services/admin/system_service.py +126 -0
- kairo/backend/services/admin/user_service.py +157 -0
- kairo/backend/services/agent_service.py +107 -0
- kairo/backend/services/api_key_service.py +66 -0
- kairo/backend/services/api_usage_service.py +126 -0
- kairo/backend/services/auth_service.py +101 -0
- kairo/backend/services/chat_service.py +501 -0
- kairo/backend/services/conversation_service.py +264 -0
- kairo/backend/services/device_auth_service.py +193 -0
- kairo/backend/services/email_service.py +55 -0
- kairo/backend/services/image_service.py +181 -0
- kairo/backend/services/llm_service.py +186 -0
- kairo/backend/services/project_service.py +109 -0
- kairo/backend/services/status_service.py +167 -0
- kairo/backend/services/stripe_service.py +78 -0
- kairo/backend/services/usage_service.py +150 -0
- kairo/backend/services/web_search_service.py +96 -0
- kairo/migrations/env.py +60 -0
- kairo/migrations/versions/001_initial.py +55 -0
- kairo/migrations/versions/002_usage_tracking_and_indexes.py +66 -0
- kairo/migrations/versions/003_username_to_email.py +21 -0
- kairo/migrations/versions/004_add_plans_and_verification.py +67 -0
- kairo/migrations/versions/005_add_projects.py +52 -0
- kairo/migrations/versions/006_add_image_generation.py +63 -0
- kairo/migrations/versions/007_add_admin_portal.py +107 -0
- kairo/migrations/versions/008_add_device_code_auth.py +76 -0
- kairo/migrations/versions/009_add_status_page.py +65 -0
- kairo/tools/extract_claude_data.py +465 -0
- kairo/tools/filter_claude_data.py +303 -0
- kairo/tools/generate_curated_data.py +157 -0
- kairo/tools/mix_training_data.py +295 -0
- kairo_code/__init__.py +3 -0
- kairo_code/agents/__init__.py +25 -0
- kairo_code/agents/architect.py +98 -0
- kairo_code/agents/audit.py +100 -0
- kairo_code/agents/base.py +463 -0
- kairo_code/agents/coder.py +155 -0
- kairo_code/agents/database.py +77 -0
- kairo_code/agents/docs.py +88 -0
- kairo_code/agents/explorer.py +62 -0
- kairo_code/agents/guardian.py +80 -0
- kairo_code/agents/planner.py +66 -0
- kairo_code/agents/reviewer.py +91 -0
- kairo_code/agents/security.py +94 -0
- kairo_code/agents/terraform.py +88 -0
- kairo_code/agents/testing.py +97 -0
- kairo_code/agents/uiux.py +88 -0
- kairo_code/auth.py +232 -0
- kairo_code/config.py +172 -0
- kairo_code/conversation.py +173 -0
- kairo_code/heartbeat.py +63 -0
- kairo_code/llm.py +291 -0
- kairo_code/logging_config.py +156 -0
- kairo_code/main.py +818 -0
- kairo_code/router.py +217 -0
- kairo_code/sandbox.py +248 -0
- kairo_code/settings.py +183 -0
- kairo_code/tools/__init__.py +51 -0
- kairo_code/tools/analysis.py +509 -0
- kairo_code/tools/base.py +417 -0
- kairo_code/tools/code.py +58 -0
- kairo_code/tools/definitions.py +617 -0
- kairo_code/tools/files.py +315 -0
- kairo_code/tools/review.py +390 -0
- kairo_code/tools/search.py +185 -0
- kairo_code/ui.py +418 -0
- kairo_code-0.1.0.dist-info/METADATA +13 -0
- kairo_code-0.1.0.dist-info/RECORD +144 -0
- kairo_code-0.1.0.dist-info/WHEEL +5 -0
- kairo_code-0.1.0.dist-info/entry_points.txt +2 -0
- 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)
|