85 lines
2.8 KiB
Python
85 lines
2.8 KiB
Python
|
from uuid import UUID, uuid4
|
||
|
from typing import Optional
|
||
|
import time
|
||
|
import re
|
||
|
|
||
|
from pydantic import BaseModel, EmailStr
|
||
|
import pydantic
|
||
|
from sqlalchemy import BigInteger, Enum
|
||
|
from sqlalchemy.orm import mapped_column, Mapped, relationship
|
||
|
import sqlalchemy as sa
|
||
|
|
||
|
from materia_server.models.base import Base
|
||
|
from materia_server.models.auth.source import LoginType
|
||
|
from materia_server.models import database
|
||
|
from loguru import logger
|
||
|
|
||
|
valid_username = re.compile(r"^[\da-zA-Z][-.\w]*$")
|
||
|
invalid_username = re.compile(r"[-._]{2,}|[-._]$")
|
||
|
|
||
|
class User(Base):
|
||
|
__tablename__ = "user"
|
||
|
|
||
|
id: Mapped[UUID] = mapped_column(primary_key = True, default = uuid4)
|
||
|
name: Mapped[str] = mapped_column(unique = True)
|
||
|
lower_name: Mapped[str] = mapped_column(unique = True)
|
||
|
full_name: Mapped[str]
|
||
|
email: Mapped[str]
|
||
|
is_email_private: Mapped[bool] = mapped_column(default = True)
|
||
|
hashed_password: Mapped[str]
|
||
|
must_change_password: Mapped[bool] = mapped_column(default = False)
|
||
|
|
||
|
login_type: Mapped["LoginType"]
|
||
|
|
||
|
created: Mapped[int] = mapped_column(BigInteger, default = time.time)
|
||
|
updated: Mapped[int] = mapped_column(BigInteger, default = time.time)
|
||
|
last_login: Mapped[int] = mapped_column(BigInteger, nullable = True)
|
||
|
|
||
|
is_active: Mapped[bool] = mapped_column(default = False)
|
||
|
is_admin: Mapped[bool] = mapped_column(default = False)
|
||
|
|
||
|
avatar: Mapped[Optional[str]]
|
||
|
|
||
|
repository: Mapped["Repository"] = relationship(back_populates = "user")
|
||
|
|
||
|
def update_last_login(self):
|
||
|
self.last_login = int(time.time())
|
||
|
|
||
|
def is_local(self) -> bool:
|
||
|
return self.login_type == LoginType.Plain
|
||
|
|
||
|
def is_oauth2(self) -> bool:
|
||
|
return self.login_type == LoginType.OAuth2
|
||
|
|
||
|
@staticmethod
|
||
|
def is_valid_username(name: str) -> bool:
|
||
|
return bool(valid_username.match(name) and not invalid_username.match(name))
|
||
|
|
||
|
@staticmethod
|
||
|
async def count(db: database.Database):
|
||
|
async with db.session() as session:
|
||
|
return await session.scalar(sa.select(sa.func.count(User.id)))
|
||
|
|
||
|
@staticmethod
|
||
|
async def by_name(name: str, db: database.Database):
|
||
|
async with db.session() as session:
|
||
|
return (await session.scalars(sa.select(User).where(User.name == name))).first()
|
||
|
|
||
|
@staticmethod
|
||
|
async def by_email(email: str, db: database.Database):
|
||
|
async with db.session() as session:
|
||
|
return (await session.scalars(sa.select(User).where(User.email == email))).first()
|
||
|
|
||
|
@staticmethod
|
||
|
async def by_id(id: UUID, db: database.Database):
|
||
|
async with db.session() as session:
|
||
|
return (await session.scalars(sa.select(User).where(User.id == id))).first()
|
||
|
|
||
|
class UserCredentials(BaseModel):
|
||
|
name: str
|
||
|
password: str
|
||
|
email: Optional[EmailStr]
|
||
|
|
||
|
|
||
|
from materia_server.models.repository.repository import Repository
|