2024-10-20 21:57:12 +00:00
import uuid
2025-07-20 22:30:04 +03:00
import logging
2024-10-20 21:57:12 +00:00
from django . db import models
from vpn . tasks import sync_user
from django . db . models . signals import post_save , pre_delete
from django . dispatch import receiver
from . server_plugins import Server
import shortuuid
2024-10-27 01:06:37 +00:00
from django . contrib . auth . models import AbstractUser
2025-07-20 22:30:04 +03:00
logger = logging . getLogger ( __name__ )
2025-07-21 13:23:10 +03:00
class UserStatistics ( models . Model ) :
user = models . ForeignKey ( ' User ' , on_delete = models . CASCADE )
server_name = models . CharField ( max_length = 256 )
acl_link_id = models . CharField ( max_length = 1024 , null = True , blank = True , help_text = " None for server-level stats " )
total_connections = models . IntegerField ( default = 0 )
recent_connections = models . IntegerField ( default = 0 )
daily_usage = models . JSONField ( default = list , help_text = " Daily connection counts for last 30 days " )
max_daily = models . IntegerField ( default = 0 )
updated_at = models . DateTimeField ( auto_now = True )
class Meta :
unique_together = [ ' user ' , ' server_name ' , ' acl_link_id ' ]
verbose_name = ' User Statistics '
verbose_name_plural = ' User Statistics '
indexes = [
models . Index ( fields = [ ' user ' , ' server_name ' ] ) ,
models . Index ( fields = [ ' updated_at ' ] ) ,
]
def __str__ ( self ) :
link_part = f " (link: { self . acl_link_id } ) " if self . acl_link_id else " (server total) "
return f " { self . user . username } - { self . server_name } { link_part } "
2025-07-20 22:50:22 +03:00
class TaskExecutionLog ( models . Model ) :
task_id = models . CharField ( max_length = 255 , help_text = " Celery task ID " )
task_name = models . CharField ( max_length = 100 , help_text = " Task name " )
server = models . ForeignKey ( ' Server ' , on_delete = models . SET_NULL , null = True , blank = True )
user = models . ForeignKey ( ' User ' , on_delete = models . SET_NULL , null = True , blank = True )
action = models . CharField ( max_length = 100 , help_text = " Action performed " )
status = models . CharField ( max_length = 20 , choices = [
( ' STARTED ' , ' Started ' ) ,
( ' SUCCESS ' , ' Success ' ) ,
( ' FAILURE ' , ' Failure ' ) ,
( ' RETRY ' , ' Retry ' ) ,
] , default = ' STARTED ' )
message = models . TextField ( help_text = " Detailed execution message " )
execution_time = models . FloatField ( null = True , blank = True , help_text = " Execution time in seconds " )
created_at = models . DateTimeField ( auto_now_add = True )
class Meta :
ordering = [ ' -created_at ' ]
verbose_name = ' Task Execution Log '
verbose_name_plural = ' Task Execution Logs '
indexes = [
models . Index ( fields = [ ' task_id ' ] ) ,
models . Index ( fields = [ ' created_at ' ] ) ,
models . Index ( fields = [ ' status ' ] ) ,
]
def __str__ ( self ) :
return f " { self . task_name } - { self . action } ( { self . status } ) "
2024-10-28 17:15:49 +00:00
class AccessLog ( models . Model ) :
user = models . CharField ( max_length = 256 , blank = True , null = True , editable = False )
server = models . CharField ( max_length = 256 , blank = True , null = True , editable = False )
2025-07-21 13:49:43 +03:00
acl_link_id = models . CharField ( max_length = 1024 , blank = True , null = True , editable = False , help_text = " ID of the ACL link used " )
2024-10-28 17:15:49 +00:00
action = models . CharField ( max_length = 100 , editable = False )
data = models . TextField ( default = " " , blank = True , editable = False )
timestamp = models . DateTimeField ( auto_now_add = True )
2025-07-21 04:18:27 +03:00
class Meta :
indexes = [
models . Index ( fields = [ ' user ' ] ) ,
models . Index ( fields = [ ' server ' ] ) ,
2025-07-21 13:49:43 +03:00
models . Index ( fields = [ ' acl_link_id ' ] ) ,
2025-07-21 04:18:27 +03:00
models . Index ( fields = [ ' timestamp ' ] ) ,
models . Index ( fields = [ ' action ' , ' timestamp ' ] ) ,
]
2024-10-28 17:15:49 +00:00
def __str__ ( self ) :
2025-07-21 13:49:43 +03:00
link_part = f " (link: { self . acl_link_id } ) " if self . acl_link_id else " "
return f " { self . action } { self . user } request for { self . server } { link_part } at { self . timestamp } "
2024-10-27 01:06:37 +00:00
class User ( AbstractUser ) :
2024-10-27 01:18:06 +00:00
#is_active = False
2024-10-28 17:15:49 +00:00
comment = models . TextField ( default = " " , blank = True , help_text = " Free form user comment " )
registration_date = models . DateTimeField ( auto_now_add = True , verbose_name = " Created " )
servers = models . ManyToManyField ( ' Server ' , through = ' ACL ' , blank = True , help_text = " Servers user has access to " )
2024-10-20 21:57:12 +00:00
last_access = models . DateTimeField ( null = True , blank = True )
2024-10-28 17:15:49 +00:00
hash = models . CharField ( max_length = 64 , unique = True , help_text = " Random user hash. It ' s using for client config generation. " )
2025-08-15 04:02:22 +03:00
# Telegram fields
telegram_user_id = models . BigIntegerField (
null = True ,
blank = True ,
unique = True ,
help_text = " Telegram user ID "
)
telegram_username = models . CharField (
max_length = 255 ,
blank = True ,
null = True ,
help_text = " Telegram username (without @) "
)
telegram_first_name = models . CharField (
max_length = 255 ,
blank = True ,
null = True ,
help_text = " First name from Telegram "
)
telegram_last_name = models . CharField (
max_length = 255 ,
blank = True ,
null = True ,
help_text = " Last name from Telegram "
)
telegram_phone = models . CharField (
max_length = 20 ,
blank = True ,
null = True ,
help_text = " Phone number from Telegram (optional) "
)
2024-10-20 21:57:12 +00:00
def get_servers ( self ) :
return Server . objects . filter ( acl__user = self )
def save ( self , * args , * * kwargs ) :
if not self . hash :
self . hash = shortuuid . ShortUUID ( ) . random ( length = 16 )
super ( ) . save ( * args , * * kwargs )
def __str__ ( self ) :
2024-10-27 01:06:37 +00:00
return self . username
2024-10-20 21:57:12 +00:00
class ACL ( models . Model ) :
user = models . ForeignKey ( ' User ' , on_delete = models . CASCADE )
server = models . ForeignKey ( ' Server ' , on_delete = models . CASCADE )
2024-10-28 17:15:49 +00:00
created_at = models . DateTimeField ( auto_now_add = True , verbose_name = " Created " )
2024-10-20 21:57:12 +00:00
class Meta :
constraints = [
models . UniqueConstraint ( fields = [ ' user ' , ' server ' ] , name = ' unique_user_server ' )
]
def __str__ ( self ) :
2024-10-27 01:06:37 +00:00
return f " { self . user . username } - { self . server . name } "
2024-10-28 17:32:06 +00:00
def save ( self , * args , * * kwargs ) :
2025-06-27 16:02:13 +03:00
# Check if this is a new ACL and if auto_create_link should be enabled
is_new = self . pk is None
auto_create_link = kwargs . pop ( ' auto_create_link ' , True )
2024-10-28 17:32:06 +00:00
super ( ) . save ( * args , * * kwargs )
2025-06-27 16:02:13 +03:00
# Only create default link for new ACLs when auto_create_link is True
# This happens when ACL is created through admin interface or initial user setup
if is_new and auto_create_link and not self . links . exists ( ) :
2024-10-28 17:32:06 +00:00
ACLLink . objects . create ( acl = self , link = shortuuid . ShortUUID ( ) . random ( length = 16 ) )
2024-10-20 21:57:12 +00:00
@receiver ( post_save , sender = ACL )
def acl_created_or_updated ( sender , instance , created , * * kwargs ) :
2025-07-20 22:30:04 +03:00
try :
sync_user . delay_on_commit ( instance . user . id , instance . server . id )
if created :
logger . info ( f " Scheduled sync for new ACL: user { instance . user . username } on server { instance . server . name } " )
else :
logger . info ( f " Scheduled sync for updated ACL: user { instance . user . username } on server { instance . server . name } " )
except Exception as e :
logger . error ( f " Failed to schedule sync task for ACL { instance . id } : { e } " )
# Don't raise exception to avoid blocking ACL creation/update
2024-10-20 21:57:12 +00:00
@receiver ( pre_delete , sender = ACL )
def acl_deleted ( sender , instance , * * kwargs ) :
2025-07-20 22:30:04 +03:00
try :
sync_user . delay_on_commit ( instance . user . id , instance . server . id )
logger . info ( f " Scheduled sync for deleted ACL: user { instance . user . username } on server { instance . server . name } " )
except Exception as e :
logger . error ( f " Failed to schedule sync task for ACL deletion { instance . id } : { e } " )
# Don't raise exception to avoid blocking ACL deletion
2024-10-28 17:15:49 +00:00
class ACLLink ( models . Model ) :
acl = models . ForeignKey ( ACL , related_name = ' links ' , on_delete = models . CASCADE )
2025-01-09 17:24:12 +00:00
comment = models . TextField ( default = " " , blank = True , help_text = " ACL link comment, device name, etc... " )
2024-11-18 20:34:54 +00:00
link = models . CharField ( max_length = 1024 , default = " " , unique = True , blank = True , null = True , verbose_name = " Access link " , help_text = " Access link to get dynamic configuration " )
2025-07-21 12:12:31 +03:00
last_access_time = models . DateTimeField ( null = True , blank = True , help_text = " Last time this link was accessed " )
2024-10-28 17:15:49 +00:00
def save ( self , * args , * * kwargs ) :
2024-10-28 17:32:06 +00:00
if self . link == " " :
2024-10-28 17:15:49 +00:00
self . link = shortuuid . ShortUUID ( ) . random ( length = 16 )
super ( ) . save ( * args , * * kwargs )
def __str__ ( self ) :
2025-06-27 16:02:13 +03:00
return self . link
2025-08-08 05:46:36 +03:00
# Import new Xray models
from . models_xray import (
2025-08-08 06:50:04 +03:00
Credentials , Certificate ,
2025-08-08 05:46:36 +03:00
Inbound , SubscriptionGroup , UserSubscription
)