1import os
2from datetime import datetime
3
4import sqlalchemy.sql
5from sqlalchemy import (
6 BigInteger,
7 Boolean,
8 Column,
9 DateTime,
10 Enum,
11 Float,
12 ForeignKey,
13 Integer,
14 String,
15 Table,
16 UniqueConstraint,
17)
18from sqlalchemy.orm import relationship
19from sqlalchemy.sql.expression import text
20
21from app.db.base import Base
22from app.models.node import NodeStatus
23from app.models.proxy import (
24 InboundHostALPN,
25 InboundHostFingerprint,
26 InboundHostSecurity,
27 ProxyTypes,
28)
29from app.models.user import ReminderType, UserDataLimitResetStrategy, UserStatus
30
31
32class Admin(Base):
33 __tablename__ = "admins"
34
35 id = Column(Integer, primary_key=True)
36 username = Column(String(32), unique=True, index=True)
37 hashed_password = Column(String(128))
38 users = relationship("User", back_populates="admin")
39 created_at = Column(DateTime, default=datetime.utcnow)
40 is_sudo = Column(Boolean, default=False)
41 password_reset_at = Column(DateTime)
42
43
44inbounds_services = Table(
45 "inbounds_services",
46 Base.metadata,
47 Column("inbound_id", ForeignKey("inbounds.id"), primary_key=True),
48 Column("service_id", ForeignKey("services.id"), primary_key=True),
49)
50
51users_services = Table(
52 "users_services",
53 Base.metadata,
54 Column("user_id", ForeignKey("users.id"), primary_key=True),
55 Column("service_id", ForeignKey("services.id"), primary_key=True),
56)
57
58
59class Service(Base):
60 __tablename__ = "services"
61 id = Column(Integer, primary_key=True)
62 name = Column(String(64))
63 # modifications = Column(String(1024))
64 users = relationship("User", secondary=users_services, back_populates="services")
65 inbounds = relationship(
66 "Inbound", secondary=inbounds_services, back_populates="services"
67 )
68
69
70class User(Base):
71 __tablename__ = "users"
72
73 id = Column(Integer, primary_key=True)
74 username = Column(String(32), unique=True, index=True)
75 key = Column(String(64), unique=True)
76 enabled = Column(
77 Boolean, nullable=False, default=True, server_default=sqlalchemy.sql.true()
78 )
79 services = relationship(
80 "Service", secondary=users_services, back_populates="users", lazy="joined"
81 )
82 inbounds = relationship(
83 "Inbound",
84 secondary="join(users_services, inbounds_services, inbounds_services.c.service_id == users_services.c.service_id)"
85 ".join(Inbound, Inbound.id == inbounds_services.c.inbound_id)",
86 viewonly=True,
87 distinct_target_key=True,
88 lazy="joined",
89 )
90 # proxies = relationship("Proxy", back_populates="user", cascade="all, delete-orphan")
91 status = Column(Enum(UserStatus), nullable=False, default=UserStatus.active)
92 used_traffic = Column(BigInteger, default=0)
93 node_usages = relationship(
94 "NodeUserUsage",
95 back_populates="user",
96 cascade="all,delete,delete-orphan",
97 lazy="joined",
98 )
99 notification_reminders = relationship(
100 "NotificationReminder",
101 back_populates="user",
102 cascade="all,delete,delete-orphan",
103 )
104 data_limit = Column(BigInteger)
105 data_limit_reset_strategy = Column(
106 Enum(UserDataLimitResetStrategy),
107 nullable=False,
108 default=UserDataLimitResetStrategy.no_reset,
109 )
110 ip_limit = Column(Integer, nullable=False, default=-1)
111 settings = Column(String)
112 usage_logs = relationship(
113 "UserUsageResetLogs", back_populates="user", lazy="joined"
114 )
115 expire = Column(DateTime)
116 admin_id = Column(Integer, ForeignKey("admins.id"))
117 admin = relationship("Admin", back_populates="users")
118 sub_updated_at = Column(DateTime)
119 sub_last_user_agent = Column(String(512))
120 created_at = Column(DateTime, default=datetime.utcnow)
121 note = Column(String(500))
122 online_at = Column(DateTime)
123 on_hold_expire_duration = Column(BigInteger)
124 on_hold_timeout = Column(DateTime)
125 edit_at = Column(DateTime)
126
127 @property
128 def lifetime_used_traffic(self):
129 return (
130 sum([log.used_traffic_at_reset for log in self.usage_logs])
131 + self.used_traffic
132 )
133
134 @property
135 def last_traffic_reset_time(self):
136 return self.usage_logs[-1].reset_at if self.usage_logs else self.created_at
137
138
139class UserUsageResetLogs(Base):
140 __tablename__ = "user_usage_logs"
141
142 id = Column(Integer, primary_key=True)
143 user_id = Column(Integer, ForeignKey("users.id"))
144 user = relationship("User", back_populates="usage_logs")
145 used_traffic_at_reset = Column(BigInteger, nullable=False)
146 reset_at = Column(DateTime, default=datetime.utcnow)
147
148
149class Inbound(Base):
150 __tablename__ = "inbounds"
151 __table_args__ = (UniqueConstraint("node_id", "tag"),)
152
153 id = Column(Integer, primary_key=True)
154 protocol = Column(Enum(ProxyTypes))
155 tag = Column(String(256), nullable=False)
156 config = Column(String(), nullable=False)
157 node_id = Column(Integer, ForeignKey("nodes.id"), index=True)
158 node = relationship("Node", back_populates="inbounds")
159 services = relationship(
160 "Service", secondary=inbounds_services, back_populates="inbounds"
161 )
162 hosts = relationship(
163 "InboundHost", back_populates="inbound", cascade="all, delete, delete-orphan"
164 )
165
166
167class InboundHost(Base):
168 __tablename__ = "hosts"
169
170 id = Column(Integer, primary_key=True)
171 remark = Column(String(256), nullable=False)
172 address = Column(String(256), nullable=False)
173 port = Column(Integer)
174 path = Column(String(256))
175 sni = Column(String(256))
176 host = Column(String(256))
177 security = Column(
178 Enum(InboundHostSecurity),
179 nullable=False,
180 default=InboundHostSecurity.inbound_default,
181 )
182 alpn = Column(
183 Enum(InboundHostALPN),
184 nullable=False,
185 default=InboundHostSecurity.none,
186 server_default=InboundHostSecurity.none.name,
187 )
188 fingerprint = Column(
189 Enum(InboundHostFingerprint),
190 nullable=False,
191 default=InboundHostSecurity.none,
192 server_default=InboundHostSecurity.none.name,
193 )
194
195 inbound_id = Column(Integer, ForeignKey("inbounds.id"), nullable=False)
196 inbound = relationship("Inbound", back_populates="hosts")
197 allowinsecure = Column(Boolean, default=False)
198 is_disabled = Column(Boolean, default=False)
199
200
201class System(Base):
202 __tablename__ = "system"
203
204 id = Column(Integer, primary_key=True)
205 uplink = Column(BigInteger, default=0)
206 downlink = Column(BigInteger, default=0)
207
208
209class JWT(Base):
210 __tablename__ = "jwt"
211
212 id = Column(Integer, primary_key=True)
213 secret_key = Column(
214 String(64), nullable=False, default=lambda: os.urandom(32).hex()
215 )
216
217
218class TLS(Base):
219 __tablename__ = "tls"
220
221 id = Column(Integer, primary_key=True)
222 key = Column(String(4096), nullable=False)
223 certificate = Column(String(2048), nullable=False)
224
225
226class Node(Base):
227 __tablename__ = "nodes"
228 __table_args__ = (UniqueConstraint("address", "port"),)
229 id = Column(Integer, primary_key=True)
230 name = Column(String(256), unique=True)
231 connection_backend = Column(String())
232 address = Column(String(256))
233 port = Column(Integer)
234 xray_version = Column(String(32))
235 inbounds = relationship("Inbound", back_populates="node", cascade="all, delete")
236 status = Column(Enum(NodeStatus), nullable=False, default=NodeStatus.unhealthy)
237 last_status_change = Column(DateTime, default=datetime.utcnow)
238 message = Column(String(1024))
239 created_at = Column(DateTime, default=datetime.utcnow)
240 uplink = Column(BigInteger, default=0)
241 downlink = Column(BigInteger, default=0)
242 user_usages = relationship(
243 "NodeUserUsage", back_populates="node", cascade="all, delete, delete-orphan"
244 )
245 usages = relationship(
246 "NodeUsage", back_populates="node", cascade="all, delete, delete-orphan"
247 )
248 usage_coefficient = Column(
249 Float, nullable=False, server_default=text("1.0"), default=1
250 )
251
252
253class NodeUserUsage(Base):
254 __tablename__ = "node_user_usages"
255 __table_args__ = (UniqueConstraint("created_at", "user_id", "node_id"),)
256
257 id = Column(Integer, primary_key=True)
258 created_at = Column(DateTime, nullable=False) # one hour per record
259 user_id = Column(Integer, ForeignKey("users.id"))
260 user = relationship("User", back_populates="node_usages")
261 node_id = Column(Integer, ForeignKey("nodes.id"))
262 node = relationship("Node", back_populates="user_usages")
263 used_traffic = Column(BigInteger, default=0)
264
265
266class NodeUsage(Base):
267 __tablename__ = "node_usages"
268 __table_args__ = (UniqueConstraint("created_at", "node_id"),)
269
270 id = Column(Integer, primary_key=True)
271 created_at = Column(DateTime, nullable=False) # one hour per record
272 node_id = Column(Integer, ForeignKey("nodes.id"))
273 node = relationship("Node", back_populates="usages")
274 uplink = Column(BigInteger, default=0)
275 downlink = Column(BigInteger, default=0)
276
277
278class NotificationReminder(Base):
279 __tablename__ = "notification_reminders"
280
281 id = Column(Integer, primary_key=True)
282 user_id = Column(Integer, ForeignKey("users.id"))
283 user = relationship("User", back_populates="notification_reminders")
284 type = Column(Enum(ReminderType), nullable=False)
285 expires_at = Column(DateTime)
286 created_at = Column(DateTime, default=datetime.utcnow)