mirror of
https://git.ugnet.gay/CrossTalk/azul.git
synced 2026-05-27 22:59:49 +00:00
219 lines
8.1 KiB
Python
219 lines
8.1 KiB
Python
from typing import Any, Iterator
|
|
from datetime import datetime
|
|
from contextlib import contextmanager
|
|
import sqlalchemy as sa
|
|
from sqlalchemy.orm import declarative_base, sessionmaker, relationship
|
|
|
|
from util.json_type import JSONType
|
|
import settings
|
|
|
|
def Col(*args: Any, **kwargs: Any) -> sa.Column:
|
|
if 'nullable' not in kwargs:
|
|
kwargs['nullable'] = False
|
|
return sa.Column(*args, **kwargs)
|
|
|
|
class Base(declarative_base()): # type: ignore
|
|
__abstract__ = True
|
|
|
|
class WithFrontData(Base):
|
|
__abstract__ = True
|
|
|
|
# Data specific to front-ends; e.g. different types of password hashes
|
|
# E.g. front_data = { 'msn': { ... }, 'ymsg': { ... }, ... }
|
|
_front_data = Col(JSONType, name = 'front_data', default = {})
|
|
|
|
def set_front_data(self, frontend: str, key: str, value: Any) -> None:
|
|
fd = self._front_data or {}
|
|
if frontend not in fd:
|
|
fd[frontend] = {}
|
|
fd[frontend][key] = value
|
|
# As a side-effect, this also makes `._front_data` into a new object,
|
|
# so SQLAlchemy picks up the fact that it's been changed.
|
|
# (SQLAlchemy only does shallow comparisons on fields by default.)
|
|
self._front_data = _simplify_json_data(fd)
|
|
|
|
def get_front_data(self, frontend: str, key: str) -> Any:
|
|
fd = self._front_data
|
|
if not fd: return None
|
|
fd = fd.get(frontend)
|
|
if not fd: return None
|
|
return fd.get(key)
|
|
|
|
class User(WithFrontData):
|
|
__tablename__ = 'user'
|
|
|
|
id = Col(sa.Integer, primary_key = True)
|
|
date_created = Col(sa.DateTime, default = datetime.utcnow)
|
|
date_login = Col(sa.DateTime, nullable = True)
|
|
uuid = Col(sa.String(50), unique = True)
|
|
email = Col(sa.String(80))
|
|
username = Col(sa.String(40))
|
|
first_name = Col(sa.String(50), default = 'John')
|
|
middle_name = Col(sa.String(50), nullable = True)
|
|
last_name = Col(sa.String(50), default = 'Doe')
|
|
# specifically used for social features/member directory
|
|
nickname = Col(sa.String(60), nullable = True)
|
|
uin = Col(sa.BigInteger, nullable = True)
|
|
# verified for logging in. all existing users pre-email verification have this set to true, until a certain date, unless they verified their email (account_verified), then it's always true. this is set to false on all new users post-verification unless they've also verified their e-mail address
|
|
verified_to_login = Col(sa.Boolean)
|
|
# Roaming name - can be null and (in theory) stays constant to what the user sets it to
|
|
name = Col(sa.String(60), nullable = True)
|
|
name_last_modified = Col(sa.DateTime, default = datetime.utcnow)
|
|
# Friendly name set during IM sessions. It cannot be null and is more prone to being overwritten than the roaming name
|
|
friendly_name = Col(sa.String(60))
|
|
# Roaming message
|
|
message = Col(sa.String(255), nullable = True)
|
|
message_last_modified = Col(sa.DateTime, default = datetime.utcnow)
|
|
password = Col(sa.String(250))
|
|
groups = Col(JSONType)
|
|
settings = Col(JSONType)
|
|
suspended = Col(sa.Boolean)
|
|
is_tester = Col(sa.Boolean)
|
|
is_mvp = Col(sa.Boolean)
|
|
show_in_dir = Col(sa.Boolean)
|
|
evil_permanent = Col(sa.Integer, default = 0)
|
|
evil_temporary = Col(sa.Integer, default = 0)
|
|
alias_active = Col(sa.Boolean, default=False)
|
|
did_firsttime_email_change = Col(sa.Boolean, default=False)
|
|
account_verified = Col(sa.Boolean, default=False) # for e-mail address verification
|
|
lang = Col(sa.String(10), nullable = True)
|
|
profile = relationship("UserProfile", backref="user", uselist=False, cascade="all, delete-orphan")
|
|
__table_args__ = (sa.Index('email_ci_index', sa.text('(LOWER(email))'), unique = True), sa.Index('username_ci_index', sa.text('(LOWER(username))'), unique = True))
|
|
|
|
class UserContact(WithFrontData):
|
|
__tablename__ = 'user_contact'
|
|
|
|
user_id = Col(sa.Integer, sa.ForeignKey('user.id'), primary_key = True)
|
|
contact_id = Col(sa.Integer, sa.ForeignKey('user.id'), primary_key = True)
|
|
user_uuid = Col(sa.String(50), sa.ForeignKey('user.uuid')) # = User(self.user_id).uuid
|
|
|
|
uuid = Col(sa.String(50), sa.ForeignKey('user.uuid')) # = User(self.contact_id).uuid
|
|
name = Col(sa.String(100))
|
|
lists = Col(sa.Integer)
|
|
pending = Col(sa.Boolean, default = False)
|
|
groups = Col(JSONType)
|
|
is_messenger_user = Col(sa.Boolean)
|
|
|
|
index_id = Col(sa.String(50))
|
|
birthdate = Col(sa.DateTime, nullable = True)
|
|
anniversary = Col(sa.DateTime, nullable = True)
|
|
notes = Col(sa.String(255), nullable = True)
|
|
first_name = Col(sa.String(50), nullable = True)
|
|
middle_name = Col(sa.String(50), nullable = True)
|
|
last_name = Col(sa.String(50), nullable = True)
|
|
nickname = Col(sa.String(80), nullable = True)
|
|
primary_email_type = Col(sa.String(10), nullable = True)
|
|
personal_email = Col(sa.String(80), nullable = True)
|
|
work_email = Col(sa.String(80), nullable = True)
|
|
im_email = Col(sa.String(80), nullable = True)
|
|
other_email = Col(sa.String(80), nullable = True)
|
|
home_phone = Col(sa.String(50), nullable = True)
|
|
work_phone = Col(sa.String(50), nullable = True)
|
|
fax_phone = Col(sa.String(50), nullable = True)
|
|
pager_phone = Col(sa.String(50), nullable = True)
|
|
mobile_phone = Col(sa.String(50), nullable = True)
|
|
other_phone = Col(sa.String(50), nullable = True)
|
|
personal_website = Col(sa.String(80), nullable = True)
|
|
business_website = Col(sa.String(80), nullable = True)
|
|
locations = Col(JSONType, default = {})
|
|
|
|
class UserProfile(WithFrontData):
|
|
__tablename__ = 'user_profile'
|
|
|
|
user_id = Col(sa.Integer, sa.ForeignKey('user.id'), primary_key = True)
|
|
bio = Col(sa.Text, nullable = True)
|
|
pronouns = Col(sa.String(40), nullable = True)
|
|
website = Col(sa.String(100), nullable = True)
|
|
socials = Col(JSONType, nullable = True)
|
|
streetaddr = Col(sa.String(100), nullable = True)
|
|
city = Col(sa.String(80), nullable = True)
|
|
state = Col(sa.String(80), nullable = True)
|
|
zip = Col(sa.Integer, nullable = True)
|
|
country = Col(sa.String(60), nullable = True)
|
|
language = Col(sa.String(50), nullable = True)
|
|
interests = Col(JSONType, nullable = True, default = {})
|
|
visibility = Col(sa.String(25), default='public')
|
|
|
|
class Circle(Base):
|
|
__tablename__ = 'circle'
|
|
|
|
id = Col(sa.Integer, primary_key = True)
|
|
chat_id = Col(sa.String(50), unique = True)
|
|
name = Col(sa.String(100))
|
|
owner_id = Col(sa.Integer, sa.ForeignKey('user.id'))
|
|
owner_uuid = Col(sa.String(50), sa.ForeignKey('user.uuid'))
|
|
owner_friendly = Col(sa.String(100))
|
|
membership_access = Col(sa.Integer)
|
|
request_membership_option = Col(sa.Integer)
|
|
|
|
class CircleMembership(Base):
|
|
__tablename__ = 'circle_membership'
|
|
|
|
id = Col(sa.Integer, primary_key = True)
|
|
chat_id = Col(sa.String(50), sa.ForeignKey('circle.chat_id'))
|
|
member_id = Col(sa.Integer, sa.ForeignKey('user.id'))
|
|
member_uuid = Col(sa.String(50), sa.ForeignKey('user.uuid'))
|
|
role = Col(sa.Integer)
|
|
state = Col(sa.Integer)
|
|
blocking = Col(sa.Boolean)
|
|
inviter_uuid = Col(sa.String(50), nullable = True)
|
|
inviter_email = Col(sa.String(100), nullable = True)
|
|
inviter_name = Col(sa.String(100), nullable = True)
|
|
invite_message = Col(sa.String(250), nullable = True)
|
|
|
|
class Sound(Base):
|
|
__tablename__ = 'sound'
|
|
|
|
hash = Col(sa.String(50), primary_key = True)
|
|
title = Col(sa.String(100))
|
|
category = Col(sa.Integer)
|
|
language = Col(sa.Integer)
|
|
is_public = Col(sa.Boolean)
|
|
hits = Col(sa.Integer, default = 0)
|
|
|
|
class LoginToken(Base):
|
|
__tablename__ = 'login_token'
|
|
|
|
id = Col(sa.Integer, primary_key = True)
|
|
token = Col(sa.String(100))
|
|
purpose = Col(sa.String(25))
|
|
data = Col(JSONType)
|
|
expiry = Col(sa.DateTime)
|
|
|
|
def _simplify_json_data(data: Any) -> Any:
|
|
if isinstance(data, dict):
|
|
d = {}
|
|
for k, v in data.items():
|
|
v = _simplify_json_data(v)
|
|
if v is not None:
|
|
d[k] = v
|
|
if not d:
|
|
return None
|
|
return d
|
|
if isinstance(data, (list, tuple)):
|
|
return [_simplify_json_data(x) for x in data]
|
|
return data
|
|
|
|
engine = sa.create_engine(settings.DB, echo='debug' if settings.DEBUG and settings.DEBUG_FULL and settings.DEBUG_LOG_SQL_QUERIES else None)
|
|
session_factory = sessionmaker(bind = engine)
|
|
|
|
@contextmanager
|
|
def Session() -> Iterator[Any]:
|
|
if Session._depth > 0: # type: ignore
|
|
yield Session._global # type: ignore
|
|
return
|
|
session = session_factory()
|
|
Session._global = session # type: ignore
|
|
Session._depth += 1 # type: ignore
|
|
try:
|
|
yield session
|
|
session.commit()
|
|
except:
|
|
session.rollback()
|
|
raise
|
|
finally:
|
|
session.close()
|
|
Session._global = None # type: ignore
|
|
Session._depth -= 1 # type: ignore
|
|
Session._global = None # type: ignore
|
|
Session._depth = 0 # type: ignore |