upd
This commit is contained in:
1 parent
373c832e71
commit
1b0dfdafbc
48 files changed
+3114
-396
No files matched your search
@@ -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 +0,0 @@
|
||||
from app.core.models.content.user_content import UserContent
|
||||
@@ -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)
|
||||
@@ -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()
|
||||
Reference in new issue
Block a user