This commit is contained in:
user committed 2025-07-11 06:46:33 +03:00
1 parent 373c832e71
commit 1b0dfdafbc
48 files changed
+3114 -396

No files matched your search

+2 -2
View File
@@ -1,9 +1,9 @@
from app.core.models.base import AlchemyBase
from app.core.models.keys import KnownKey
from app.core.models.memory import Memory
from app.core.models.node_storage import StoredContent
# from app.core.models.node_storage import StoredContent # Disabled to avoid conflicts
from app.core.models.transaction import UserBalance, InternalTransaction, StarsInvoice
from app.core.models.user import User
from app.core.models.user.user import User, UserSession, UserRole, UserStatus, ApiKey
from app.core.models.wallet_connection import WalletConnection
from app.core.models.messages import KnownTelegramMessage
from app.core.models.user_activity import UserActivity
-1
View File
@@ -1 +0,0 @@
from app.core.models.content.user_content import UserContent
+16 -21
View File
@@ -1,48 +1,43 @@
from sqlalchemy import Column, BigInteger, Integer, String, ForeignKey, DateTime, JSON, Boolean
from datetime import datetime
from sqlalchemy import Column, BigInteger, Integer, String, ForeignKey, JSON, Boolean
from sqlalchemy.dialects.postgresql import TIMESTAMP
from sqlalchemy.orm import relationship
from app.core.models.base import AlchemyBase
from app.core.models.content.indexation_mixins import UserContentIndexationMixin
from app.core.models.base import BaseModel
class UserContent(AlchemyBase, UserContentIndexationMixin):
class UserContent(BaseModel):
__tablename__ = 'users_content'
id = Column(Integer, autoincrement=True, primary_key=True)
type = Column(String(128), nullable=False) # 'license/issuer', 'license/listen', 'nft/unknown'
onchain_address = Column(String(1024), nullable=True) # bind by this
# Legacy compatibility fields
type = Column(String(128), nullable=False, default='license/listen')
onchain_address = Column(String(1024), nullable=True)
owner_address = Column(String(1024), nullable=True)
code_hash = Column(String(128), nullable=True)
data_hash = Column(String(128), nullable=True)
updated = Column(DateTime, nullable=False, default=0)
content_id = Column(Integer, ForeignKey('node_storage.id'), nullable=True)
created = Column(DateTime, nullable=False, default=0)
content_id = Column(String(36), ForeignKey('my_network_content.id'), nullable=True)
meta = Column(JSON, nullable=False, default={})
user_id = Column(Integer, ForeignKey('users.id'), nullable=False)
wallet_connection_id = Column(Integer, ForeignKey('wallet_connections.id'), nullable=True)
status = Column(String(64), nullable=False, default='active') # 'transaction_requested'
meta = Column(JSON, nullable=False, default=dict)
user_id = Column(String(36), ForeignKey('users.id'), nullable=False)
wallet_connection_id = Column(String(36), ForeignKey('wallet_connections.id'), nullable=True)
user = relationship('User', uselist=False, foreign_keys=[user_id])
wallet_connection = relationship('WalletConnection', uselist=False, foreign_keys=[wallet_connection_id])
content = relationship('StoredContent', uselist=False, foreign_keys=[content_id])
class UserAction(AlchemyBase):
class UserAction(BaseModel):
__tablename__ = 'users_actions'
id = Column(Integer, autoincrement=True, primary_key=True)
type = Column(String(128), nullable=False) # 'purchase'
user_id = Column(Integer, ForeignKey('users.id'), nullable=False)
content_id = Column(Integer, ForeignKey('node_storage.id'), nullable=True)
user_id = Column(String(36), ForeignKey('users.id'), nullable=False)
content_id = Column(String(36), ForeignKey('my_network_content.id'), nullable=True)
telegram_message_id = Column(BigInteger, nullable=True)
to_address = Column(String(1024), nullable=True)
from_address = Column(String(1024), nullable=True)
status = Column(String(128), nullable=True)
meta = Column(JSON, nullable=False, default={})
created = Column(DateTime, nullable=False, default=0)
meta = Column(JSON, nullable=False, default=dict)
user = relationship('User', uselist=False, foreign_keys=[user_id])
content = relationship('StoredContent', uselist=False, foreign_keys=[content_id])
@@ -3,13 +3,13 @@ Content models with async support and enhanced features
"""
import hashlib
import mimetypes
from datetime import datetime
from datetime import datetime, timedelta
from enum import Enum
from pathlib import Path
from typing import Optional, List, Dict, Any, Union
from urllib.parse import urljoin
from sqlalchemy import Column, String, Integer, BigInteger, Boolean, Text, ForeignKey, Index, text
from sqlalchemy import Column, String, Integer, BigInteger, Boolean, Text, ForeignKey, Index, text, DateTime
from sqlalchemy.dialects.postgresql import JSONB, ARRAY
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.future import select
@@ -61,7 +61,7 @@ class LicenseType(str, Enum):
class StoredContent(BaseModel):
"""Enhanced content storage model"""
__tablename__ = 'stored_content'
__tablename__ = 'my_network_content'
# Content identification
hash = Column(
@@ -487,7 +487,7 @@ class UserContent(BaseModel):
# Content relationship
content_id = Column(
String(36), # UUID
ForeignKey('stored_content.id'),
ForeignKey('my_network_content.id'),
nullable=False,
index=True,
comment="Reference to stored content"
@@ -728,4 +728,174 @@ class EncryptionKey(BaseModel):
def revoke(self) -> None:
"""Revoke the key"""
self.revoked_at = datetime.utcnow()
self.revoked_at = datetime.utcnow()
# Backward compatibility aliases
Content = StoredContent
class ContentChunk(BaseModel):
"""Content chunk for large file uploads"""
__tablename__ = 'content_chunks'
# Chunk identification
content_id = Column(
String(36), # UUID
ForeignKey('my_network_content.id'),
nullable=False,
index=True,
comment="Parent content ID"
)
chunk_index = Column(
Integer,
nullable=False,
comment="Chunk sequence number"
)
chunk_hash = Column(
String(128),
nullable=False,
index=True,
comment="Hash of this chunk"
)
# Chunk data
chunk_size = Column(
Integer,
nullable=False,
comment="Size of this chunk in bytes"
)
chunk_data = Column(
Text,
nullable=True,
comment="Base64 encoded chunk data (for small chunks)"
)
file_path = Column(
String(1024),
nullable=True,
comment="Path to chunk file (for large chunks)"
)
# Upload status
uploaded = Column(
Boolean,
nullable=False,
default=False,
comment="Whether chunk is uploaded"
)
# Relationships
content = relationship('StoredContent', back_populates='chunks')
def __str__(self) -> str:
return f"ContentChunk({self.id}, content={self.content_id}, index={self.chunk_index})"
class FileUpload(BaseModel):
"""File upload session tracking"""
__tablename__ = 'file_uploads'
# Upload identification
upload_id = Column(
String(128),
nullable=False,
unique=True,
index=True,
comment="Unique upload session ID"
)
filename = Column(
String(512),
nullable=False,
comment="Original filename"
)
# Upload metadata
total_size = Column(
BigInteger,
nullable=False,
comment="Total file size in bytes"
)
uploaded_size = Column(
BigInteger,
nullable=False,
default=0,
comment="Uploaded size in bytes"
)
chunk_size = Column(
Integer,
nullable=False,
default=1048576, # 1MB
comment="Chunk size in bytes"
)
total_chunks = Column(
Integer,
nullable=False,
comment="Total number of chunks"
)
uploaded_chunks = Column(
Integer,
nullable=False,
default=0,
comment="Number of uploaded chunks"
)
# Upload status
upload_status = Column(
String(32),
nullable=False,
default='pending',
comment="Upload status"
)
# User information
user_id = Column(
String(36), # UUID
ForeignKey('users.id'),
nullable=True,
index=True,
comment="User performing the upload"
)
# Completion
content_id = Column(
String(36), # UUID
ForeignKey('my_network_content.id'),
nullable=True,
comment="Final content ID after completion"
)
# Relationships
user = relationship('User', back_populates='file_uploads')
content = relationship('StoredContent', back_populates='file_upload')
def __str__(self) -> str:
return f"FileUpload({self.id}, upload_id={self.upload_id}, status={self.upload_status})"
@property
def progress_percentage(self) -> float:
"""Get upload progress percentage"""
if self.total_size == 0:
return 0.0
return (self.uploaded_size / self.total_size) * 100.0
@property
def is_complete(self) -> bool:
"""Check if upload is complete"""
return self.uploaded_size >= self.total_size and self.upload_status == 'completed'
def update_progress(self, chunk_size: int) -> None:
"""Update upload progress"""
self.uploaded_size += chunk_size
self.uploaded_chunks += 1
if self.uploaded_size >= self.total_size:
self.upload_status = 'completed'
elif self.upload_status == 'pending':
self.upload_status = 'uploading'
# Update relationships in StoredContent
StoredContent.chunks = relationship('ContentChunk', back_populates='content')
StoredContent.file_upload = relationship('FileUpload', back_populates='content', uselist=False)
+10 -32
View File
@@ -1,35 +1,13 @@
from datetime import datetime
from sqlalchemy import Column, Integer, String, BigInteger, DateTime, JSON
from sqlalchemy.orm import relationship
# Import and re-export models from the user.py module inside this directory
from .user import User, UserSession, UserRole, UserStatus, ApiKey
from app.core.auth_v1 import AuthenticationMixin as AuthenticationMixin_V1
from app.core.models.user.display_mixin import DisplayMixin
from app.core.models.user.wallet_mixin import WalletMixin
from app.core.translation import TranslationCore
from ..base import AlchemyBase
class User(AlchemyBase, DisplayMixin, TranslationCore, AuthenticationMixin_V1, WalletMixin):
LOCALE_DOMAIN = 'sanic_telegram_bot'
__tablename__ = 'users'
id = Column(Integer, autoincrement=True, primary_key=True)
telegram_id = Column(BigInteger, nullable=False)
username = Column(String(512), nullable=True)
lang_code = Column(String(8), nullable=False, default="en")
meta = Column(JSON, nullable=False, default={})
last_use = Column(DateTime, nullable=False, default=datetime.utcnow)
updated = Column(DateTime, nullable=False, default=datetime.utcnow)
created = Column(DateTime, nullable=False, default=datetime.utcnow)
balances = relationship('UserBalance', back_populates='user')
internal_transactions = relationship('InternalTransaction', back_populates='user')
wallet_connections = relationship('WalletConnection', back_populates='user')
# stored_content = relationship('StoredContent', back_populates='user')
def __str__(self):
return f"User, {self.id}_{self.telegram_id} | Username: {self.username} " + '\\'
# Keep backward compatibility
__all__ = [
'User',
'UserSession',
'UserRole',
'UserStatus',
'ApiKey'
]
@@ -7,7 +7,7 @@ from datetime import datetime, timedelta
from typing import Optional, List, Dict, Any
from enum import Enum
from sqlalchemy import Column, String, BigInteger, Boolean, Integer, Index, text
from sqlalchemy import Column, String, BigInteger, Boolean, Integer, Index, text, DateTime
from sqlalchemy.dialects.postgresql import ARRAY, JSONB
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.future import select
@@ -181,7 +181,7 @@ class User(BaseModel):
Index('idx_users_telegram_id', 'telegram_id'),
Index('idx_users_username', 'username'),
Index('idx_users_role_status', 'role', 'status'),
Index('idx_users_last_activity', 'last_activity'),
Index('idx_users_last_activity', 'last_use'), # Use actual database column name
Index('idx_users_created_at', 'created_at'),
)
@@ -417,4 +417,181 @@ class User(BaseModel):
'is_verified': self.is_verified,
'is_premium': self.is_premium,
'created_at': self.created_at.isoformat() if self.created_at else None
}
}
class UserSession(BaseModel):
"""User session model for authentication tracking"""
__tablename__ = 'user_sessions'
user_id = Column(
BigInteger,
nullable=False,
index=True,
comment="Associated user ID"
)
refresh_token_hash = Column(
String(255),
nullable=False,
comment="Hashed refresh token"
)
ip_address = Column(
String(45),
nullable=True,
comment="Session IP address"
)
user_agent = Column(
String(512),
nullable=True,
comment="User agent string"
)
expires_at = Column(
DateTime,
nullable=False,
comment="Session expiration time"
)
last_used_at = Column(
DateTime,
nullable=True,
comment="Last time session was used"
)
logged_out_at = Column(
DateTime,
nullable=True,
comment="Session logout time"
)
is_active = Column(
Boolean,
nullable=False,
default=True,
comment="Whether session is active"
)
remember_me = Column(
Boolean,
nullable=False,
default=False,
comment="Whether this is a remember me session"
)
# Indexes for performance
__table_args__ = (
Index('idx_user_sessions_user_id', 'user_id'),
Index('idx_user_sessions_expires_at', 'expires_at'),
Index('idx_user_sessions_active', 'is_active'),
)
def __str__(self) -> str:
return f"UserSession({self.id}, user_id={self.user_id}, active={self.is_active})"
def is_expired(self) -> bool:
"""Check if session is expired"""
return datetime.utcnow() > self.expires_at
def is_valid(self) -> bool:
"""Check if session is valid and active"""
return self.is_active and not self.is_expired() and not self.logged_out_at
class UserRole(BaseModel):
"""User role model for permissions"""
__tablename__ = 'user_roles'
name = Column(
String(64),
nullable=False,
unique=True,
index=True,
comment="Role name"
)
description = Column(
String(255),
nullable=True,
comment="Role description"
)
permissions = Column(
ARRAY(String),
nullable=False,
default=list,
comment="Role permissions list"
)
is_system = Column(
Boolean,
nullable=False,
default=False,
comment="Whether this is a system role"
)
def __str__(self) -> str:
return f"UserRole({self.name})"
def has_permission(self, permission: str) -> bool:
"""Check if role has specific permission"""
return permission in (self.permissions or [])
class ApiKey(BaseModel):
"""API key model for programmatic access"""
__tablename__ = 'api_keys'
user_id = Column(
BigInteger,
nullable=False,
index=True,
comment="Associated user ID"
)
name = Column(
String(128),
nullable=False,
comment="API key name"
)
key_hash = Column(
String(255),
nullable=False,
unique=True,
comment="Hashed API key"
)
permissions = Column(
ARRAY(String),
nullable=False,
default=list,
comment="API key permissions"
)
expires_at = Column(
DateTime,
nullable=True,
comment="API key expiration time"
)
last_used_at = Column(
DateTime,
nullable=True,
comment="Last time key was used"
)
is_active = Column(
Boolean,
nullable=False,
default=True,
comment="Whether key is active"
)
# Indexes for performance
__table_args__ = (
Index('idx_api_keys_user_id', 'user_id'),
Index('idx_api_keys_hash', 'key_hash'),
Index('idx_api_keys_active', 'is_active'),
)
def __str__(self) -> str:
return f"ApiKey({self.id}, name={self.name}, user_id={self.user_id})"
def is_expired(self) -> bool:
"""Check if API key is expired"""
if not self.expires_at:
return False
return datetime.utcnow() > self.expires_at
def is_valid(self) -> bool:
"""Check if API key is valid and active"""
return self.is_active and not self.is_expired()