Files
adii1823 0a0ceaf7d7 ch14
2021-10-28 17:41:38 +05:30

182 lines
4.3 KiB
Python

# api_code/api/models.py
import enum
import hashlib
import os
import secrets
from sqlalchemy import (
Column,
DateTime,
Enum,
Float,
ForeignKey,
Integer,
Unicode,
)
from sqlalchemy.orm import relationship
from .database import Base
UNICODE_LEN = 128
SALT_LEN = 64
# Enums
class Classes(str, enum.Enum):
first = "first"
second = "second"
class Roles(str, enum.Enum):
admin = "admin"
passenger = "passenger"
# Models
class Station(Base):
__tablename__ = "station"
id = Column(Integer, primary_key=True)
code = Column(
Unicode(UNICODE_LEN), nullable=False, unique=True
)
country = Column(Unicode(UNICODE_LEN), nullable=False)
city = Column(Unicode(UNICODE_LEN), nullable=False)
departures = relationship(
"Train",
foreign_keys="[Train.station_from_id]",
back_populates="station_from",
)
arrivals = relationship(
"Train",
foreign_keys="[Train.station_to_id]",
back_populates="station_to",
)
def __repr__(self):
return f"<{self.code}: id={self.id} city={self.city}>"
__str__ = __repr__
class Train(Base):
__tablename__ = "train"
id = Column(Integer, primary_key=True)
name = Column(Unicode(UNICODE_LEN), nullable=False)
station_from_id = Column(
ForeignKey("station.id"), nullable=False
)
station_from = relationship(
"Station",
foreign_keys=[station_from_id],
back_populates="departures",
)
station_to_id = Column(
ForeignKey("station.id"), nullable=False
)
station_to = relationship(
"Station",
foreign_keys=[station_to_id],
back_populates="arrivals",
)
departs_at = Column(DateTime(timezone=True), nullable=False)
arrives_at = Column(DateTime(timezone=True), nullable=False)
first_class = Column(Integer, default=0, nullable=False)
second_class = Column(Integer, default=0, nullable=False)
seats_per_car = Column(Integer, default=0, nullable=False)
tickets = relationship("Ticket", back_populates="train")
def __repr__(self):
return f"<{self.name}: id={self.id}>"
__str__ = __repr__
class Ticket(Base):
__tablename__ = "ticket"
id = Column(Integer, primary_key=True)
created_at = Column(DateTime(timezone=True), nullable=False)
user_id = Column(ForeignKey("user.id"), nullable=False)
user = relationship(
"User", foreign_keys=[user_id], back_populates="tickets"
)
train_id = Column(ForeignKey("train.id"), nullable=False)
train = relationship(
"Train", foreign_keys=[train_id], back_populates="tickets"
)
price = Column(Float, default=0, nullable=False)
car_class = Column(Enum(Classes), nullable=False)
def __repr__(self):
return (
f"<id={self.id} user={self.user} train={self.train}>"
)
__str__ = __repr__
class User(Base):
__tablename__ = "user"
pwd_separator = "#"
id = Column(Integer, primary_key=True)
full_name = Column(Unicode(UNICODE_LEN), nullable=False)
email = Column(Unicode(256), nullable=False, unique=True)
password = Column(Unicode(256), nullable=False)
role = Column(Enum(Roles), nullable=False)
tickets = relationship("Ticket", back_populates="user")
def is_valid_password(self, password: str):
"""Tell if password matches the one stored in DB."""
salt, stored_hash = self.password.split(
self.pwd_separator
)
_, computed_hash = _hash(
password=password, salt=bytes.fromhex(salt)
)
return secrets.compare_digest(stored_hash, computed_hash)
@classmethod
def hash_password(cls, password: str, salt: bytes = None):
salt, hashed = _hash(password=password, salt=salt)
return f"{salt}{cls.pwd_separator}{hashed}"
def __repr__(self):
return (
f"<{self.full_name}: id={self.id} "
f"role={self.role.name}>"
)
__str__ = __repr__
def _hash(password: str, salt: bytes = None):
if salt is None:
salt = os.urandom(SALT_LEN)
iterations = 100 # should be at least 100k for SHA-256
hashed = hashlib.pbkdf2_hmac(
"sha256",
password.encode("utf-8"),
salt,
iterations,
dklen=128,
)
return salt.hex(), hashed.hex()