diff --git a/models/download.py b/models/download.py index c52ef92..74f24c3 100644 --- a/models/download.py +++ b/models/download.py @@ -45,6 +45,15 @@ class Download(BaseModel, table=True): ), ) + delete_at: Optional[datetime] = Field( + default=None, + sa_column=Column( + DateTime, + nullable=True, + comment="删除时间", + ), + ) + # 外键 user_id: int = Field(foreign_key="users.id", index=True, description="所属用户ID") task_id: Optional[int] = Field(default=None, foreign_key="tasks.id", index=True, description="关联的任务ID") diff --git a/models/file.py b/models/file.py index 9af8416..59a155e 100644 --- a/models/file.py +++ b/models/file.py @@ -41,6 +41,15 @@ class File(BaseModel, table=True): ), ) + delete_at: Optional[datetime] = Field( + default=None, + sa_column=Column( + DateTime, + nullable=True, + comment="删除时间", + ), + ) + # 外键 user_id: int = Field(foreign_key="users.id", index=True, description="所属用户ID") folder_id: int = Field(foreign_key="folders.id", index=True, description="所在目录ID") diff --git a/models/migration.py b/models/migration.py new file mode 100644 index 0000000..5204e6e --- /dev/null +++ b/models/migration.py @@ -0,0 +1,99 @@ +from .setting import Setting +from pkg.conf.appmeta import BackendVersion +from pkg.password.pwd import Password + +default_settings: list[Setting] = [ + Setting(name="siteURL", value="http://localhost", type="basic"), + Setting(name="siteName", value="DiskNext", type="basic"), + Setting(name="register_enabled", value="1", type="register"), + Setting(name="default_group", value="2", type="register"), + Setting(name="siteKeywords", value="网盘,网盘", type="basic"), + Setting(name="siteDes", value="DiskNext", type="basic"), + Setting(name="siteTitle", value="云星启智", type="basic"), + Setting(name="fromName", value="DiskNext", type="mail"), + Setting(name="mail_keepalive", value="30", type="mail"), + Setting(name="fromAdress", value="no-reply@yxqi.cn", type="mail"), + Setting(name="smtpHost", value="smtp.yxqi.cn", type="mail"), + Setting(name="smtpPort", value="25", type="mail"), + Setting(name="replyTo", value="feedback@yxqi.cn", type="mail"), + Setting(name="smtpUser", value="no-reply@yxqi.cn", type="mail"), + Setting(name="smtpPass", value="", type="mail"), + Setting(name="maxEditSize", value="4194304", type="file_edit"), + Setting(name="archive_timeout", value="60", type="timeout"), + Setting(name="download_timeout", value="60", type="timeout"), + Setting(name="preview_timeout", value="60", type="timeout"), + Setting(name="doc_preview_timeout", value="60", type="timeout"), + Setting(name="upload_credential_timeout", value="1800", type="timeout"), + Setting(name="upload_session_timeout", value="86400", type="timeout"), + Setting(name="slave_api_timeout", value="60", type="timeout"), + Setting(name="onedrive_monitor_timeout", value="600", type="timeout"), + Setting(name="share_download_session_timeout", value="2073600", type="timeout"), + Setting(name="onedrive_callback_check", value="20", type="timeout"), + Setting(name="aria2_call_timeout", value="5", type="timeout"), + Setting(name="onedrive_chunk_retries", value="1", type="retry"), + Setting(name="onedrive_source_timeout", value="1800", type="timeout"), + Setting(name="reset_after_upload_failed", value="0", type="upload"), + Setting(name="login_captcha", value="0", type="login"), + Setting(name="reg_captcha", value="0", type="login"), + Setting(name="email_active", value="0", type="register"), + Setting(name="mail_activation_template", value="""激活您的账户
激活{siteTitle}账户
亲爱的{userName}
感谢您注册{siteTitle},请点击下方按钮完成账户激活。
激活账户
感谢您选择{siteTitle}。
""", type="mail_template"), + Setting(name="forget_captcha", value="0", type="login"), + Setting(name="mail_reset_pwd_template", value="""重设密码
重设{siteTitle}密码
亲爱的{userName}
请点击下方按钮完成密码重设。如果非你本人操作,请忽略此邮件。
重设密码
感谢您选择{siteTitle}。
""", type="mail_template"), + Setting(name=f"db_version_{BackendVersion}", value="installed", type="version"), + Setting(name="hot_share_num", value="10", type="share"), + Setting(name="gravatar_server", value="https://www.gravatar.com/", type="avatar"), + Setting(name="defaultTheme", value="#3f51b5", type="basic"), + Setting(name="themes", value={"#3f51b5":{"palette":{"primary":{"main":"#3f51b5"},"secondary":{"main":"#f50057"}}},"#2196f3":{"palette":{"primary":{"main":"#2196f3"},"secondary":{"main":"#FFC107"}}},"#673AB7":{"palette":{"primary":{"main":"#673AB7"},"secondary":{"main":"#2196F3"}}},"#E91E63":{"palette":{"primary":{"main":"#E91E63"},"secondary":{"main":"#42A5F5","contrastText":"#fff"}}},"#FF5722":{"palette":{"primary":{"main":"#FF5722"},"secondary":{"main":"#3F51B5"}}},"#FFC107":{"palette":{"primary":{"main":"#FFC107"},"secondary":{"main":"#26C6DA"}}},"#8BC34A":{"palette":{"primary":{"main":"#8BC34A","contrastText":"#fff"},"secondary":{"main":"#FF8A65","contrastText":"#fff"}}},"#009688":{"palette":{"primary":{"main":"#009688"},"secondary":{"main":"#4DD0E1","contrastText":"#fff"}}},"#607D8B":{"palette":{"primary":{"main":"#607D8B"},"secondary":{"main":"#F06292"}}},"#795548":{"palette":{"primary":{"main":"#795548"},"secondary":{"main":"#4CAF50","contrastText":"#fff"}}}}, type="basic"), + Setting(name="aria2_token", value="", type="aria2"), + Setting(name="aria2_rpcurl", value="", type="aria2"), + Setting(name="aria2_temp_path", value="", type="aria2"), + Setting(name="aria2_options", value="{}", type="aria2"), + Setting(name="aria2_interval", value="60", type="aria2"), + Setting(name="max_worker_num", value="10", type="task"), + Setting(name="max_parallel_transfer", value="4", type="task"), + Setting(name="secret_key", value=Password.generate(256), type="auth"), + Setting(name="temp_path", value="temp", type="path"), + Setting(name="avatar_path", value="avatar", type="path"), + Setting(name="avatar_size", value="2097152", type="avatar"), + Setting(name="avatar_size_l", value="200", type="avatar"), + Setting(name="avatar_size_m", value="130", type="avatar"), + Setting(name="avatar_size_s", value="50", type="avatar"), + Setting(name="home_view_method", value="icon", type="view"), + Setting(name="share_view_method", value="list", type="view"), + Setting(name="cron_garbage_collect", value="@hourly", type="cron"), + Setting(name="authn_enabled", value="0", type="authn"), + Setting(name="captcha_height", value="60", type="captcha"), + Setting(name="captcha_width", value="240", type="captcha"), + Setting(name="captcha_mode", value="3", type="captcha"), + Setting(name="captcha_ComplexOfNoiseText", value="0", type="captcha"), + Setting(name="captcha_ComplexOfNoiseDot", value="0", type="captcha"), + Setting(name="captcha_IsShowHollowLine", value="0", type="captcha"), + Setting(name="captcha_IsShowNoiseDot", value="1", type="captcha"), + Setting(name="captcha_IsShowNoiseText", value="0", type="captcha"), + Setting(name="captcha_IsShowSlimeLine", value="1", type="captcha"), + Setting(name="captcha_IsShowSineLine", value="0", type="captcha"), + Setting(name="captcha_CaptchaLen", value="6", type="captcha"), + Setting(name="captcha_IsUseReCaptcha", value="0", type="captcha"), + Setting(name="captcha_ReCaptchaKey", value="defaultKey", type="captcha"), + Setting(name="captcha_ReCaptchaSecret", value="defaultSecret", type="captcha"), + Setting(name="thumb_width", value="400", type="thumb"), + Setting(name="thumb_height", value="300", type="thumb"), + Setting(name="pwa_small_icon", value="/static/img/favicon.ico", type="pwa"), + Setting(name="pwa_medium_icon", value="/static/img/logo192.png", type="pwa"), + Setting(name="pwa_large_icon", value="/static/img/logo512.png", type="pwa"), + Setting(name="pwa_display", value="standalone", type="pwa"), + Setting(name="pwa_theme_color", value="#000000", type="pwa"), + Setting(name="pwa_background_color", value="#ffffff", type="pwa"), +] \ No newline at end of file diff --git a/models/node.py b/models/node.py index 76b3949..a7ccb1f 100644 --- a/models/node.py +++ b/models/node.py @@ -40,6 +40,15 @@ class Node(BaseModel, table=True): comment="更新时间", ), ) + + delete_at: Optional[datetime] = Field( + default=None, + sa_column=Column( + DateTime, + nullable=True, + comment="删除时间", + ), + ) # 关系 downloads: list["Download"] = Relationship(back_populates="node") \ No newline at end of file diff --git a/models/order.py b/models/order.py index 5aa6e1a..6a9da95 100644 --- a/models/order.py +++ b/models/order.py @@ -40,6 +40,15 @@ class Order(BaseModel, table=True): ), ) + delete_at: Optional[datetime] = Field( + default=None, + sa_column=Column( + DateTime, + nullable=True, + comment="删除时间", + ), + ) + # 外键 user_id: int = Field(foreign_key="users.id", index=True, description="所属用户ID") diff --git a/models/policy.py b/models/policy.py index 2b0652b..9e62dff 100644 --- a/models/policy.py +++ b/models/policy.py @@ -47,6 +47,15 @@ class Policy(BaseModel, table=True): ), ) + delete_at: Optional[datetime] = Field( + default=None, + sa_column=Column( + DateTime, + nullable=True, + comment="删除时间", + ), + ) + # 关系 files: List["File"] = Relationship(back_populates="policy") folders: List["Folder"] = Relationship(back_populates="policy") \ No newline at end of file diff --git a/models/report.py b/models/report.py index f64a27a..87c3a4f 100644 --- a/models/report.py +++ b/models/report.py @@ -34,6 +34,15 @@ class Report(BaseModel, table=True): ), ) + delete_at: Optional[datetime] = Field( + default=None, + sa_column=Column( + DateTime, + nullable=True, + comment="删除时间", + ), + ) + # 外键 share_id: int = Field(foreign_key="shares.id", index=True, description="被举报的分享ID") diff --git a/models/setting.py b/models/setting.py index 75fe289..bbeeafe 100644 --- a/models/setting.py +++ b/models/setting.py @@ -1,10 +1,39 @@ # my_project/models/setting.py -from typing import Optional +from typing import Optional, Literal from sqlmodel import Field, UniqueConstraint, Column, func, DateTime from .base import BaseModel from datetime import datetime +SETTINGS_TYPE = Literal[ + "auth", + "authn", + "avatar", + "basic", + "captcha", + "cron", + "file_edit", + "login", + "mail", + "mail_template", + "mobile", + "path", + "preview", + "pwa", + "register", + "retry", + "share", + "slave", + "task", + "thumb", + "timeout", + "upload", + "version", + "view", + "wopi" +] + +# 数据库模型 class Setting(BaseModel, table=True): __tablename__ = 'settings' __table_args__ = (UniqueConstraint("type", "name", name="uq_setting_type_name"),) @@ -31,4 +60,26 @@ class Setting(BaseModel, table=True): onupdate=func.now(), comment="更新时间", ), - ) \ No newline at end of file + ) + + delete_at: Optional[datetime] = Field( + default=None, + sa_column=Column( + DateTime, + nullable=True, + comment="删除时间", + ), + ) + +async def add( + type: SETTINGS_TYPE, + name: str, + value: Optional[str] = None +): + pass + +async def get( + type: SETTINGS_TYPE, + name: str +): + pass \ No newline at end of file diff --git a/pkg/password/pwd.py b/pkg/password/pwd.py new file mode 100644 index 0000000..322d410 --- /dev/null +++ b/pkg/password/pwd.py @@ -0,0 +1,59 @@ +import secrets + +class Password: + + @staticmethod + def generate( + length: int = 16, + url_safe: bool = False + ) -> str: + """ + 生成一个随机密码。 + + :param length: 密码长度,默认为 `16` 个字符。 + :param url_safe: 是否生成URL安全的密码,默认为 `False` 。 + :return: 生成的随机密码字符串。 + """ + if url_safe: + return secrets.token_urlsafe(length) + return secrets.token_hex(length) + + @staticmethod + def hash( + password: str, + ) -> str: + """ + 生成密码的加盐哈希值。 + + :return: 包含盐值和哈希值的字符串。 + :rtype: str + """ + import os, hashlib, binascii + salt = hashlib.sha256(os.urandom(60)).hexdigest().encode('ascii') + pwdhash = hashlib.pbkdf2_hmac('sha256', password.encode('utf-8'), salt, 100000) + pwdhash = binascii.hexlify(pwdhash) + return (salt + pwdhash).decode('ascii') + + @staticmethod + def verify( + stored_password: str, + provided_password: str, + ) -> bool: + """ + 验证存储的密码哈希值与用户提供的密码是否匹配。 + + :param stored_password: 存储的密码哈希值(包含盐值)。 + :param provided_password: 用户提供的密码。 + :param debug: 是否输出调试信息,将会输出原密码和哈希值。 + :return: 如果密码匹配返回 `True` ,否则返回 `False` 。 + """ + import hashlib, binascii + salt = stored_password[:64] + stored_password = stored_password[64:] + pwdhash = hashlib.pbkdf2_hmac('sha256', + provided_password.encode('utf-8'), + salt.encode('ascii'), + 100000) + pwdhash = binascii.hexlify(pwdhash).decode('ascii') + + return pwdhash == stored_password \ No newline at end of file