init
This commit is contained in:
29
app/db/__init__.py
Normal file
29
app/db/__init__.py
Normal file
@ -0,0 +1,29 @@
|
||||
from sqlalchemy import create_engine, QueuePool
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
|
||||
from app.core.config import settings
|
||||
|
||||
# 数据库引擎
|
||||
Engine = create_engine(f"sqlite:///{settings.CONFIG_PATH}/user.db",
|
||||
pool_pre_ping=True,
|
||||
echo=False,
|
||||
poolclass=QueuePool,
|
||||
pool_size=1000,
|
||||
pool_recycle=60 * 10,
|
||||
max_overflow=0)
|
||||
# 数据库会话
|
||||
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=Engine)
|
||||
|
||||
|
||||
def get_db():
|
||||
"""
|
||||
获取数据库会话
|
||||
:return: Session
|
||||
"""
|
||||
db = None
|
||||
try:
|
||||
db = SessionLocal()
|
||||
yield db
|
||||
finally:
|
||||
if db:
|
||||
db.close()
|
BIN
app/db/__pycache__/__init__.cpython-310.pyc
Normal file
BIN
app/db/__pycache__/__init__.cpython-310.pyc
Normal file
Binary file not shown.
BIN
app/db/__pycache__/init.cpython-310.pyc
Normal file
BIN
app/db/__pycache__/init.cpython-310.pyc
Normal file
Binary file not shown.
BIN
app/db/__pycache__/sites.cpython-310.pyc
Normal file
BIN
app/db/__pycache__/sites.cpython-310.pyc
Normal file
Binary file not shown.
BIN
app/db/__pycache__/subscribes.cpython-310.pyc
Normal file
BIN
app/db/__pycache__/subscribes.cpython-310.pyc
Normal file
Binary file not shown.
BIN
app/db/__pycache__/systemconfigs.cpython-310.pyc
Normal file
BIN
app/db/__pycache__/systemconfigs.cpython-310.pyc
Normal file
Binary file not shown.
BIN
app/db/__pycache__/userauth.cpython-310.pyc
Normal file
BIN
app/db/__pycache__/userauth.cpython-310.pyc
Normal file
Binary file not shown.
42
app/db/init.py
Normal file
42
app/db/init.py
Normal file
@ -0,0 +1,42 @@
|
||||
from alembic.command import upgrade
|
||||
from alembic.config import Config
|
||||
|
||||
from app.core.config import settings
|
||||
from app.core.security import get_password_hash
|
||||
from app.db import Engine, SessionLocal
|
||||
from app.db.models import Base
|
||||
from app.db.models.user import User
|
||||
from app.log import logger
|
||||
|
||||
|
||||
def init_db():
|
||||
"""
|
||||
初始化数据库
|
||||
"""
|
||||
Base.metadata.create_all(bind=Engine)
|
||||
# 初始化超级管理员
|
||||
_db = SessionLocal()
|
||||
user = User.get_by_email(db=_db, email=settings.SUPERUSER)
|
||||
if not user:
|
||||
user = User(
|
||||
full_name="Admin",
|
||||
email=settings.SUPERUSER,
|
||||
hashed_password=get_password_hash(settings.SUPERUSER_PASSWORD),
|
||||
is_superuser=True,
|
||||
)
|
||||
user.create(_db)
|
||||
|
||||
|
||||
def update_db():
|
||||
"""
|
||||
更新数据库
|
||||
"""
|
||||
db_location = settings.CONFIG_PATH / 'user.db'
|
||||
script_location = settings.ROOT_PATH / 'alembic'
|
||||
try:
|
||||
alembic_cfg = Config()
|
||||
alembic_cfg.set_main_option('script_location', str(script_location))
|
||||
alembic_cfg.set_main_option('sqlalchemy.url', f"sqlite:///{db_location}")
|
||||
upgrade(alembic_cfg, 'head')
|
||||
except Exception as e:
|
||||
logger(f'数据库更新失败:{e}')
|
42
app/db/models/__init__.py
Normal file
42
app/db/models/__init__.py
Normal file
@ -0,0 +1,42 @@
|
||||
from typing import Any
|
||||
|
||||
from sqlalchemy.orm import as_declarative, declared_attr
|
||||
|
||||
|
||||
@as_declarative()
|
||||
class Base:
|
||||
id: Any
|
||||
__name__: str
|
||||
|
||||
def create(self, db):
|
||||
db.add(self)
|
||||
db.commit()
|
||||
db.refresh(self)
|
||||
return self
|
||||
|
||||
@classmethod
|
||||
def get(cls, db, rid: int):
|
||||
return db.query(cls).filter(cls.id == rid).first()
|
||||
|
||||
def update(self, db, payload: dict):
|
||||
payload = {k: v for k, v in payload.items() if v is not None}
|
||||
for key, value in payload.items():
|
||||
setattr(self, key, value)
|
||||
db.commit()
|
||||
db.refresh(self)
|
||||
|
||||
@classmethod
|
||||
def delete(cls, db, rid):
|
||||
db.query(cls).filter(cls.id == rid).delete()
|
||||
db.commit()
|
||||
|
||||
@classmethod
|
||||
def list(cls, db):
|
||||
return db.query(cls).all()
|
||||
|
||||
def to_dict(self):
|
||||
return {c.name: getattr(self, c.name, None) for c in self.__table__.columns}
|
||||
|
||||
@declared_attr
|
||||
def __tablename__(self) -> str:
|
||||
return self.__name__.lower()
|
BIN
app/db/models/__pycache__/__init__.cpython-310.pyc
Normal file
BIN
app/db/models/__pycache__/__init__.cpython-310.pyc
Normal file
Binary file not shown.
BIN
app/db/models/__pycache__/site.cpython-310.pyc
Normal file
BIN
app/db/models/__pycache__/site.cpython-310.pyc
Normal file
Binary file not shown.
BIN
app/db/models/__pycache__/subscribe.cpython-310.pyc
Normal file
BIN
app/db/models/__pycache__/subscribe.cpython-310.pyc
Normal file
Binary file not shown.
BIN
app/db/models/__pycache__/systemconfig.cpython-310.pyc
Normal file
BIN
app/db/models/__pycache__/systemconfig.cpython-310.pyc
Normal file
Binary file not shown.
BIN
app/db/models/__pycache__/user.cpython-310.pyc
Normal file
BIN
app/db/models/__pycache__/user.cpython-310.pyc
Normal file
Binary file not shown.
28
app/db/models/site.py
Normal file
28
app/db/models/site.py
Normal file
@ -0,0 +1,28 @@
|
||||
from datetime import datetime
|
||||
|
||||
from sqlalchemy import Boolean, Column, Integer, String, Sequence
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.db.models import Base
|
||||
|
||||
|
||||
class Site(Base):
|
||||
id = Column(Integer, Sequence('id'), primary_key=True, index=True)
|
||||
name = Column(String, nullable=False)
|
||||
domain = Column(String, index=True)
|
||||
url = Column(String, nullable=False)
|
||||
pri = Column(Integer)
|
||||
rss = Column(String)
|
||||
cookie = Column(String)
|
||||
ua = Column(String)
|
||||
filter = Column(String)
|
||||
note = Column(String)
|
||||
limit_interval = Column(Integer)
|
||||
limit_count = Column(Integer)
|
||||
limit_seconds = Column(Integer)
|
||||
is_active = Column(Boolean(), default=True)
|
||||
lst_mod_date = Column(String, default=datetime.now().strftime("%Y-%m-%d %H:%M:%S"))
|
||||
|
||||
@staticmethod
|
||||
def get_by_domain(db: Session, domain: str):
|
||||
return db.query(Site).filter(Site.domain == domain).first()
|
36
app/db/models/subscribe.py
Normal file
36
app/db/models/subscribe.py
Normal file
@ -0,0 +1,36 @@
|
||||
from sqlalchemy import Column, Integer, String, Sequence
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.db.models import Base
|
||||
|
||||
|
||||
class Subscribe(Base):
|
||||
id = Column(Integer, Sequence('id'), primary_key=True, index=True)
|
||||
name = Column(String, nullable=False, index=True)
|
||||
year = Column(String)
|
||||
type = Column(String)
|
||||
keyword = Column(String)
|
||||
tmdbid = Column(String, index=True)
|
||||
doubanid = Column(String)
|
||||
season = Column(Integer)
|
||||
image = Column(String)
|
||||
description = Column(String)
|
||||
filter = Column(String)
|
||||
include = Column(String)
|
||||
exclude = Column(String)
|
||||
total_episode = Column(Integer)
|
||||
start_episode = Column(Integer)
|
||||
lack_episode = Column(Integer)
|
||||
note = Column(String)
|
||||
state = Column(String, nullable=False, index=True, default='N')
|
||||
|
||||
@staticmethod
|
||||
def exists(db: Session, tmdbid: str, season: int = None):
|
||||
if season:
|
||||
return db.query(Subscribe).filter(Subscribe.tmdbid == tmdbid,
|
||||
Subscribe.season == season).first()
|
||||
return db.query(Subscribe).filter(Subscribe.tmdbid == tmdbid).first()
|
||||
|
||||
@staticmethod
|
||||
def get_by_state(db: Session, state: str):
|
||||
return db.query(Subscribe).filter(Subscribe.state == state).all()
|
19
app/db/models/systemconfig.py
Normal file
19
app/db/models/systemconfig.py
Normal file
@ -0,0 +1,19 @@
|
||||
from sqlalchemy import Column, Integer, String, Sequence
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.db.models import Base
|
||||
|
||||
|
||||
class SystemConfig(Base):
|
||||
id = Column(Integer, Sequence('id'), primary_key=True, index=True)
|
||||
key = Column(String, index=True)
|
||||
value = Column(String, nullable=True)
|
||||
|
||||
@staticmethod
|
||||
def get_by_key(db: Session, key: str):
|
||||
return db.query(SystemConfig).filter(SystemConfig.key == key).first()
|
||||
|
||||
@staticmethod
|
||||
def delete_by_key(db: Session, key: str):
|
||||
db.query(SystemConfig).filter(SystemConfig.key == key).delete()
|
||||
db.commit()
|
27
app/db/models/user.py
Normal file
27
app/db/models/user.py
Normal file
@ -0,0 +1,27 @@
|
||||
from sqlalchemy import Boolean, Column, Integer, String, Sequence
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.core.security import verify_password
|
||||
from app.db.models import Base
|
||||
|
||||
|
||||
class User(Base):
|
||||
id = Column(Integer, Sequence('id'), primary_key=True, index=True)
|
||||
full_name = Column(String, index=True)
|
||||
email = Column(String, unique=True, index=True, nullable=False)
|
||||
hashed_password = Column(String, nullable=False)
|
||||
is_active = Column(Boolean(), default=True)
|
||||
is_superuser = Column(Boolean(), default=False)
|
||||
|
||||
@staticmethod
|
||||
def authenticate(db: Session, email: str, password: str):
|
||||
user = db.query(User).filter(User.email == email).first()
|
||||
if not user:
|
||||
return None
|
||||
if not verify_password(password, str(user.hashed_password)):
|
||||
return None
|
||||
return user
|
||||
|
||||
@staticmethod
|
||||
def get_by_email(db: Session, email: str):
|
||||
return db.query(User).filter(User.email == email).first()
|
56
app/db/sites.py
Normal file
56
app/db/sites.py
Normal file
@ -0,0 +1,56 @@
|
||||
from typing import Tuple, List
|
||||
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.db import SessionLocal
|
||||
from app.db.models.site import Site
|
||||
|
||||
|
||||
class Sites:
|
||||
"""
|
||||
站点管理
|
||||
"""
|
||||
_db: Session = None
|
||||
|
||||
def __init__(self, _db=SessionLocal()):
|
||||
self._db = _db
|
||||
|
||||
def add(self, **kwargs) -> Tuple[bool, str]:
|
||||
"""
|
||||
新增站点
|
||||
"""
|
||||
site = Site(**kwargs)
|
||||
if not site.get_by_domain(self._db, kwargs.get("domain")):
|
||||
site.create(self._db)
|
||||
return True, "新增站点成功"
|
||||
return False, "站点已存在"
|
||||
|
||||
def list(self) -> List[Site]:
|
||||
"""
|
||||
获取站点列表
|
||||
"""
|
||||
return Site.list(self._db)
|
||||
|
||||
def get_by_domain(self, domain: str) -> Site:
|
||||
"""
|
||||
按域名获取站点
|
||||
"""
|
||||
return Site.get_by_domain(self._db, domain)
|
||||
|
||||
def exists(self, domain: str) -> bool:
|
||||
"""
|
||||
判断站点是否存在
|
||||
"""
|
||||
return Site.get_by_domain(self._db, domain) is not None
|
||||
|
||||
def update_cookie(self, domain: str, cookies: str) -> Tuple[bool, str]:
|
||||
"""
|
||||
更新站点Cookie
|
||||
"""
|
||||
site = Site.get_by_domain(self._db, domain)
|
||||
if not site:
|
||||
return False, "站点不存在"
|
||||
site.update(self._db, {
|
||||
"cookie": cookies
|
||||
})
|
||||
return True, "更新站点Cookie成功"
|
76
app/db/subscribes.py
Normal file
76
app/db/subscribes.py
Normal file
@ -0,0 +1,76 @@
|
||||
from typing import Tuple, List
|
||||
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.core import MediaInfo
|
||||
from app.db import SessionLocal
|
||||
from app.db.models.subscribe import Subscribe
|
||||
from app.utils.types import MediaType
|
||||
|
||||
|
||||
class Subscribes:
|
||||
"""
|
||||
订阅管理
|
||||
"""
|
||||
_db: Session = None
|
||||
|
||||
def __init__(self, _db=SessionLocal()):
|
||||
self._db = _db
|
||||
|
||||
def add(self, mediainfo: MediaInfo, **kwargs) -> Tuple[bool, str]:
|
||||
"""
|
||||
新增订阅
|
||||
"""
|
||||
# 总集数
|
||||
if mediainfo.type == MediaType.TV:
|
||||
if not kwargs.get('season'):
|
||||
kwargs.update({
|
||||
'season': 1
|
||||
})
|
||||
if not kwargs.get('total_episode'):
|
||||
total_episode = len(mediainfo.seasons.get(kwargs.get('season')) or [])
|
||||
if not total_episode:
|
||||
return False, "未识别到总集数"
|
||||
kwargs.update({
|
||||
'total_episode': total_episode
|
||||
})
|
||||
subscribe = Subscribe(name=mediainfo.title,
|
||||
year=mediainfo.year,
|
||||
type=mediainfo.type.value,
|
||||
tmdbid=mediainfo.tmdb_id,
|
||||
image=mediainfo.get_poster_image(),
|
||||
description=mediainfo.overview,
|
||||
**kwargs)
|
||||
if not subscribe.exists(self._db, tmdbid=mediainfo.tmdb_id, season=kwargs.get('season')):
|
||||
subscribe.create(self._db)
|
||||
return True, "新增订阅成功"
|
||||
else:
|
||||
return False, "订阅已存在"
|
||||
|
||||
def get(self, sid: int) -> Subscribe:
|
||||
"""
|
||||
获取订阅
|
||||
"""
|
||||
return Subscribe.get(self._db, rid=sid)
|
||||
|
||||
def list(self, state: str = None) -> List[Subscribe]:
|
||||
"""
|
||||
获取订阅列表
|
||||
"""
|
||||
if state:
|
||||
return Subscribe.get_by_state(self._db, state)
|
||||
return Subscribe.list(self._db)
|
||||
|
||||
def delete(self, sid: int):
|
||||
"""
|
||||
删除订阅
|
||||
"""
|
||||
Subscribe.delete(self._db, rid=sid)
|
||||
|
||||
def update(self, sid: int, payload: dict):
|
||||
"""
|
||||
更新订阅
|
||||
"""
|
||||
subscribe = self.get(sid)
|
||||
subscribe.update(self._db, payload)
|
||||
return subscribe
|
58
app/db/systemconfigs.py
Normal file
58
app/db/systemconfigs.py
Normal file
@ -0,0 +1,58 @@
|
||||
import json
|
||||
from typing import Any, Union
|
||||
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.db import SessionLocal
|
||||
from app.db.models.systemconfig import SystemConfig
|
||||
from app.utils.object import ObjectUtils
|
||||
from app.utils.singleton import Singleton
|
||||
from app.utils.types import SystemConfigKey
|
||||
|
||||
|
||||
class SystemConfigs(metaclass=Singleton):
|
||||
# 配置对象
|
||||
__SYSTEMCONF: dict = {}
|
||||
_db: Session = None
|
||||
|
||||
def __init__(self, _db=SessionLocal()):
|
||||
"""
|
||||
加载配置到内存
|
||||
"""
|
||||
self._db = _db
|
||||
for item in SystemConfig.list(self._db):
|
||||
if ObjectUtils.is_obj(item.value):
|
||||
self.__SYSTEMCONF[item.key] = json.loads(item.value)
|
||||
else:
|
||||
self.__SYSTEMCONF[item.key] = item.value
|
||||
|
||||
def set(self, key: Union[str, SystemConfigKey], value: Any):
|
||||
"""
|
||||
设置系统设置
|
||||
"""
|
||||
if isinstance(key, SystemConfigKey):
|
||||
key = key.value
|
||||
# 更新内存
|
||||
self.__SYSTEMCONF[key] = value
|
||||
# 写入数据库
|
||||
if ObjectUtils.is_obj(value):
|
||||
if value is not None:
|
||||
value = json.dumps(value)
|
||||
else:
|
||||
value = ''
|
||||
conf = SystemConfig.get_by_key(self._db, key)
|
||||
if conf:
|
||||
conf.update(self._db, {"value": value})
|
||||
else:
|
||||
conf = SystemConfig(key=key, value=value)
|
||||
conf.create(self._db)
|
||||
|
||||
def get(self, key: Union[str, SystemConfigKey] = None):
|
||||
"""
|
||||
获取系统设置
|
||||
"""
|
||||
if isinstance(key, SystemConfigKey):
|
||||
key = key.value
|
||||
if not key:
|
||||
return self.__SYSTEMCONF
|
||||
return self.__SYSTEMCONF.get(key)
|
46
app/db/userauth.py
Normal file
46
app/db/userauth.py
Normal file
@ -0,0 +1,46 @@
|
||||
import jwt
|
||||
from fastapi import Depends, HTTPException, status
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app import schemas
|
||||
from app.core import settings, security
|
||||
from app.core.security import reusable_oauth2
|
||||
from app.db import get_db
|
||||
from app.db.models.user import User
|
||||
|
||||
|
||||
def get_current_user(
|
||||
db: Session = Depends(get_db), token: str = Depends(reusable_oauth2)
|
||||
) -> User:
|
||||
try:
|
||||
payload = jwt.decode(
|
||||
token, settings.SECRET_KEY, algorithms=[security.ALGORITHM]
|
||||
)
|
||||
token_data = schemas.TokenPayload(**payload)
|
||||
except (jwt.DecodeError, jwt.InvalidTokenError, jwt.ImmatureSignatureError):
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail="token校验不通过",
|
||||
)
|
||||
user = User.get(db, rid=token_data.sub)
|
||||
if not user:
|
||||
raise HTTPException(status_code=404, detail="用户不存在")
|
||||
return user
|
||||
|
||||
|
||||
def get_current_active_user(
|
||||
current_user: User = Depends(get_current_user),
|
||||
) -> User:
|
||||
if not current_user.is_active:
|
||||
raise HTTPException(status_code=400, detail="用户未激活")
|
||||
return current_user
|
||||
|
||||
|
||||
def get_current_active_superuser(
|
||||
current_user: User = Depends(get_current_user),
|
||||
) -> User:
|
||||
if not current_user.is_superuser:
|
||||
raise HTTPException(
|
||||
status_code=400, detail="用户权限不足"
|
||||
)
|
||||
return current_user
|
Reference in New Issue
Block a user