Browse Source

🎨 [pre-commit.ci] Auto format from pre-commit.com hooks

pull/13907/head
pre-commit-ci[bot] 2 weeks ago
parent
commit
d7f439e757
  1. 2
      .copier/update_dotenv.py
  2. 1
      backend/Dockerfile
  3. 2
      backend/app/api/main.py
  4. 67
      backend/app/api/routes/ip.py
  5. 2
      backend/app/email-templates/build/new_account.html
  6. 2
      backend/app/email-templates/build/reset_password.html
  7. 2
      backend/app/email-templates/build/test_email.html
  8. 12
      backend/app/tests/scripts/test_backend_pre_start.py
  9. 12
      backend/app/tests/scripts/test_test_pre_start.py
  10. 2
      development.md
  11. 2
      frontend/.gitignore
  12. 2
      frontend/src/components/Pending/PendingItems.tsx
  13. 2
      frontend/src/components/UserSettings/DeleteAccount.tsx
  14. 4
      frontend/src/components/ui/password-input.tsx
  15. 2
      frontend/src/hooks/useCustomToast.ts
  16. 6
      frontend/src/i18n/index.ts
  17. 2
      frontend/src/routes/_layout/admin.tsx
  18. 2
      frontend/src/routes/_layout/items.tsx
  19. 4
      frontend/src/routes/_layout/settings.tsx
  20. 1
      hooks/post_gen_project.py

2
.copier/update_dotenv.py

@ -1,5 +1,5 @@
from pathlib import Path
import json
from pathlib import Path
# Update the .env file with the answers from the .copier-answers.yml file
# without using Jinja2 templates in the .env file, this way the code works as is

1
backend/Dockerfile

@ -42,4 +42,3 @@ RUN --mount=type=cache,target=/root/.cache/uv \
# CMD ["fastapi", "run", "--workers", "1", "app/main.py"]
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000", "--workers", "1", "--proxy-headers", "--forwarded-allow-ips", "*"]

2
backend/app/api/main.py

@ -1,6 +1,6 @@
from fastapi import APIRouter
from app.api.routes import items, login, private, users, utils, ip
from app.api.routes import ip, items, login, private, users, utils
from app.core.config import settings
api_router = APIRouter()

67
backend/app/api/routes/ip.py

@ -1,6 +1,7 @@
from fastapi import APIRouter, Request, Response, HTTPException
import ipaddress
from typing import Dict, Any
from typing import Any
from fastapi import APIRouter, HTTPException, Request, Response
from app.api.deps import CurrentUser
@ -12,12 +13,12 @@ router = APIRouter(prefix="/ip", tags=["ip"])
async def get_client_ip(request: Request):
"""
获取客户端IP地址
返回客户端的公网IP地址通常是通过代理转发的X-Forwarded-For头获取
"""
# 尝试从各种HTTP头获取IP地址
client_ip = _extract_client_ip(request)
return {"ip": client_ip}
@ -25,12 +26,12 @@ async def get_client_ip(request: Request):
async def get_client_ip_text(request: Request):
"""
获取客户端IP地址纯文本格式
返回客户端的公网IP地址以纯文本格式
"""
# 尝试从各种HTTP头获取IP地址
client_ip = _extract_client_ip(request)
return Response(content=client_ip, media_type="text/plain")
@ -38,7 +39,7 @@ async def get_client_ip_text(request: Request):
async def get_request_headers(request: Request):
"""
获取请求的所有头信息
返回包含所有HTTP请求头的字典
"""
return {"headers": dict(request.headers)}
@ -49,28 +50,28 @@ async def get_request_headers(request: Request):
async def get_secure_ip(request: Request, current_user: CurrentUser):
"""
获取客户端IP地址需要登录
这是一个需要用户登录的示例端点
"""
client_ip = _extract_client_ip(request)
return {
"ip": client_ip,
"user_id": str(current_user.id),
"user_email": current_user.email
"user_email": current_user.email,
}
@router.get("/analyze/{ip_address}", response_model=Dict[str, Any])
@router.get("/analyze/{ip_address}", response_model=dict[str, Any])
async def analyze_ip(ip_address: str):
"""
分析指定的IP地址
返回IP地址的基本信息如版本是否私有等
"""
try:
ip = ipaddress.ip_address(ip_address)
result = {
"ip": str(ip),
"version": f"IPv{ip.version}",
@ -80,51 +81,51 @@ async def analyze_ip(ip_address: str):
"is_loopback": ip.is_loopback,
"is_link_local": ip.is_link_local,
}
# IPv6特有属性
if ip.version == 6:
ipv6 = ipaddress.IPv6Address(ip_address)
result.update({
"is_site_local": ipv6.is_site_local,
"ipv4_mapped": ipv6.ipv4_mapped is not None,
"teredo": ipv6.teredo is not None,
"sixtofour": ipv6.sixtofour is not None
})
result.update(
{
"is_site_local": ipv6.is_site_local,
"ipv4_mapped": ipv6.ipv4_mapped is not None,
"teredo": ipv6.teredo is not None,
"sixtofour": ipv6.sixtofour is not None,
}
)
# 尝试获取反向DNS查询名称
try:
import socket
result["reverse_pointer"] = socket.gethostbyaddr(ip_address)[0]
except (socket.herror, socket.gaierror):
result["reverse_pointer"] = None
return result
except ValueError:
raise HTTPException(
status_code=400,
detail=f"无效的IP地址: {ip_address}"
)
raise HTTPException(status_code=400, detail=f"无效的IP地址: {ip_address}")
def _extract_client_ip(request: Request) -> str:
"""
从请求中提取客户端IP地址
按照优先级尝试从不同的HTTP头获取
"""
# 检查常见的代理头
for header in [
"X-Forwarded-For",
"X-Real-IP",
"X-Forwarded-For",
"X-Real-IP",
"CF-Connecting-IP", # Cloudflare
"True-Client-IP", # Akamai/Cloudflare
"X-Client-IP"
"True-Client-IP", # Akamai/Cloudflare
"X-Client-IP",
]:
if header_value := request.headers.get(header):
# 如果是X-Forwarded-For,可能包含多个IP,取第一个
if header == "X-Forwarded-For" and "," in header_value:
return header_value.split(",")[0].strip()
return header_value.strip()
# 如果没有代理头,使用直接连接的客户端地址
return request.client.host if request.client else "unknown"
return request.client.host if request.client else "unknown"

2
backend/app/email-templates/build/new_account.html

@ -22,4 +22,4 @@
<![endif]--><!--[if !mso]><!--><link href="https://fonts.googleapis.com/css?family=Ubuntu:300,400,500,700" rel="stylesheet" type="text/css"><style type="text/css">@import url(https://fonts.googleapis.com/css?family=Ubuntu:300,400,500,700);</style><!--<![endif]--><style type="text/css">@media only screen and (min-width:480px) {
.mj-column-per-100 { width:100% !important; max-width: 100%; }
}</style><style type="text/css"></style></head><body style="background-color:#fafbfc;"><div style="background-color:#fafbfc;"><!--[if mso | IE]><table align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]--><div style="background:#ffffff;background-color:#ffffff;Margin:0px auto;max-width:600px;"><table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="background:#ffffff;background-color:#ffffff;width:100%;"><tbody><tr><td style="direction:ltr;font-size:0px;padding:40px 20px;text-align:center;vertical-align:top;"><!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:middle;width:560px;" ><![endif]--><div class="mj-column-per-100 outlook-group-fix" style="font-size:13px;text-align:left;direction:ltr;display:inline-block;vertical-align:middle;width:100%;"><table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:middle;" width="100%"><tr><td align="center" style="font-size:0px;padding:35px;word-break:break-word;"><div style="font-family:Ubuntu, Helvetica, Arial, sans-serif;font-size:20px;line-height:1;text-align:center;color:#333333;">{{ project_name }} - New Account</div></td></tr><tr><td align="center" style="font-size:0px;padding:10px 25px;padding-right:25px;padding-left:25px;word-break:break-word;"><div style="font-family:Arial, Helvetica, sans-serif;font-size:16px;line-height:1;text-align:center;color:#555555;"><span>Welcome to your new account!</span></div></td></tr><tr><td align="center" style="font-size:0px;padding:10px 25px;padding-right:25px;padding-left:25px;word-break:break-word;"><div style="font-family:Arial, Helvetica, sans-serif;font-size:16px;line-height:1;text-align:center;color:#555555;">Here are your account details:</div></td></tr><tr><td align="center" style="font-size:0px;padding:10px 25px;padding-right:25px;padding-left:25px;word-break:break-word;"><div style="font-family:Arial, Helvetica, sans-serif;font-size:16px;line-height:1;text-align:center;color:#555555;">Username: {{ username }}</div></td></tr><tr><td align="center" style="font-size:0px;padding:10px 25px;padding-right:25px;padding-left:25px;word-break:break-word;"><div style="font-family:Arial, Helvetica, sans-serif;font-size:16px;line-height:1;text-align:center;color:#555555;">Password: {{ password }}</div></td></tr><tr><td align="center" vertical-align="middle" style="font-size:0px;padding:15px 30px;word-break:break-word;"><table border="0" cellpadding="0" cellspacing="0" role="presentation" style="border-collapse:separate;line-height:100%;"><tr><td align="center" bgcolor="#009688" role="presentation" style="border:none;border-radius:8px;cursor:auto;padding:10px 25px;background:#009688;" valign="middle"><a href="{{ link }}" style="background:#009688;color:#ffffff;font-family:Ubuntu, Helvetica, Arial, sans-serif;font-size:18px;font-weight:normal;line-height:120%;Margin:0;text-decoration:none;text-transform:none;" target="_blank">Go to Dashboard</a></td></tr></table></td></tr><tr><td style="font-size:0px;padding:10px 25px;word-break:break-word;"><p style="border-top:solid 2px #cccccc;font-size:1;margin:0px auto;width:100%;"></p><!--[if mso | IE]><table align="center" border="0" cellpadding="0" cellspacing="0" style="border-top:solid 2px #cccccc;font-size:1;margin:0px auto;width:510px;" role="presentation" width="510px" ><tr><td style="height:0;line-height:0;"> &nbsp;
</td></tr></table><![endif]--></td></tr></table></div><!--[if mso | IE]></td></tr></table><![endif]--></td></tr></tbody></table></div><!--[if mso | IE]></td></tr></table><![endif]--></div></body></html>
</td></tr></table><![endif]--></td></tr></table></div><!--[if mso | IE]></td></tr></table><![endif]--></td></tr></tbody></table></div><!--[if mso | IE]></td></tr></table><![endif]--></div></body></html>

2
backend/app/email-templates/build/reset_password.html

@ -22,4 +22,4 @@
<![endif]--><!--[if !mso]><!--><link href="https://fonts.googleapis.com/css?family=Ubuntu:300,400,500,700" rel="stylesheet" type="text/css"><style type="text/css">@import url(https://fonts.googleapis.com/css?family=Ubuntu:300,400,500,700);</style><!--<![endif]--><style type="text/css">@media only screen and (min-width:480px) {
.mj-column-per-100 { width:100% !important; max-width: 100%; }
}</style><style type="text/css"></style></head><body style="background-color:#fafbfc;"><div style="background-color:#fafbfc;"><!--[if mso | IE]><table align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]--><div style="background:#ffffff;background-color:#ffffff;Margin:0px auto;max-width:600px;"><table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="background:#ffffff;background-color:#ffffff;width:100%;"><tbody><tr><td style="direction:ltr;font-size:0px;padding:40px 20px;text-align:center;vertical-align:top;"><!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:middle;width:560px;" ><![endif]--><div class="mj-column-per-100 outlook-group-fix" style="font-size:13px;text-align:left;direction:ltr;display:inline-block;vertical-align:middle;width:100%;"><table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:middle;" width="100%"><tr><td align="center" style="font-size:0px;padding:35px;word-break:break-word;"><div style="font-family:Arial, Helvetica, sans-serif;font-size:20px;line-height:1;text-align:center;color:#333333;">{{ project_name }} - Password Recovery</div></td></tr><tr><td align="center" style="font-size:0px;padding:10px 25px;padding-right:25px;padding-left:25px;word-break:break-word;"><div style="font-family:Arial, Helvetica, sans-serif;font-size:16px;line-height:1;text-align:center;color:#555555;"><span>Hello {{ username }}</span></div></td></tr><tr><td align="center" style="font-size:0px;padding:10px 25px;padding-right:25px;padding-left:25px;word-break:break-word;"><div style="font-family:Arial, Helvetica, sans-serif;font-size:16px;line-height:1;text-align:center;color:#555555;">We've received a request to reset your password. You can do it by clicking the button below:</div></td></tr><tr><td align="center" vertical-align="middle" style="font-size:0px;padding:15px 30px;word-break:break-word;"><table border="0" cellpadding="0" cellspacing="0" role="presentation" style="border-collapse:separate;line-height:100%;"><tr><td align="center" bgcolor="#009688" role="presentation" style="border:none;border-radius:8px;cursor:auto;padding:10px 25px;background:#009688;" valign="middle"><a href="{{ link }}" style="background:#009688;color:#ffffff;font-family:Ubuntu, Helvetica, Arial, sans-serif;font-size:18px;font-weight:normal;line-height:120%;Margin:0;text-decoration:none;text-transform:none;" target="_blank">Reset password</a></td></tr></table></td></tr><tr><td align="center" style="font-size:0px;padding:10px 25px;padding-right:25px;padding-left:25px;word-break:break-word;"><div style="font-family:Arial, Helvetica, sans-serif;font-size:16px;line-height:1;text-align:center;color:#555555;">Or copy and paste the following link into your browser:</div></td></tr><tr><td align="center" style="font-size:0px;padding:10px 25px;padding-right:25px;padding-left:25px;word-break:break-word;"><div style="font-family:Arial, Helvetica, sans-serif;font-size:16px;line-height:1;text-align:center;color:#555555;"><a href="{{ link }}">{{ link }}</a></div></td></tr><tr><td align="center" style="font-size:0px;padding:10px 25px;padding-right:25px;padding-left:25px;word-break:break-word;"><div style="font-family:Arial, Helvetica, sans-serif;font-size:16px;line-height:1;text-align:center;color:#555555;">This password will expire in {{ valid_hours }} hours.</div></td></tr><tr><td style="font-size:0px;padding:10px 25px;word-break:break-word;"><p style="border-top:solid 2px #cccccc;font-size:1;margin:0px auto;width:100%;"></p><!--[if mso | IE]><table align="center" border="0" cellpadding="0" cellspacing="0" style="border-top:solid 2px #cccccc;font-size:1;margin:0px auto;width:510px;" role="presentation" width="510px" ><tr><td style="height:0;line-height:0;"> &nbsp;
</td></tr></table><![endif]--></td></tr><tr><td align="center" style="font-size:0px;padding:10px 25px;padding-right:25px;padding-left:25px;word-break:break-word;"><div style="font-family:Arial, Helvetica, sans-serif;font-size:14px;line-height:1;text-align:center;color:#555555;">If you didn't request a password recovery you can disregard this email.</div></td></tr></table></div><!--[if mso | IE]></td></tr></table><![endif]--></td></tr></tbody></table></div><!--[if mso | IE]></td></tr></table><![endif]--></div></body></html>
</td></tr></table><![endif]--></td></tr><tr><td align="center" style="font-size:0px;padding:10px 25px;padding-right:25px;padding-left:25px;word-break:break-word;"><div style="font-family:Arial, Helvetica, sans-serif;font-size:14px;line-height:1;text-align:center;color:#555555;">If you didn't request a password recovery you can disregard this email.</div></td></tr></table></div><!--[if mso | IE]></td></tr></table><![endif]--></td></tr></tbody></table></div><!--[if mso | IE]></td></tr></table><![endif]--></div></body></html>

2
backend/app/email-templates/build/test_email.html

@ -22,4 +22,4 @@
<![endif]--><style type="text/css">@media only screen and (min-width:480px) {
.mj-column-per-100 { width:100% !important; max-width: 100%; }
}</style><style type="text/css"></style></head><body style="background-color:#fafbfc;"><div style="background-color:#fafbfc;"><!--[if mso | IE]><table align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]--><div style="background:#ffffff;background-color:#ffffff;Margin:0px auto;max-width:600px;"><table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="background:#ffffff;background-color:#ffffff;width:100%;"><tbody><tr><td style="direction:ltr;font-size:0px;padding:40px 20px;text-align:center;vertical-align:top;"><!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:middle;width:560px;" ><![endif]--><div class="mj-column-per-100 outlook-group-fix" style="font-size:13px;text-align:left;direction:ltr;display:inline-block;vertical-align:middle;width:100%;"><table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:middle;" width="100%"><tr><td align="center" style="font-size:0px;padding:35px;word-break:break-word;"><div style="font-family:Arial, Helvetica, sans-serif;font-size:20px;line-height:1;text-align:center;color:#333333;">{{ project_name }}</div></td></tr><tr><td align="center" style="font-size:0px;padding:10px 25px;padding-right:25px;padding-left:25px;word-break:break-word;"><div style="font-family:, sans-serif;font-size:16px;line-height:1;text-align:center;color:#555555;"><span>Test email for: {{ email }}</span></div></td></tr><tr><td style="font-size:0px;padding:10px 25px;word-break:break-word;"><p style="border-top:solid 2px #cccccc;font-size:1;margin:0px auto;width:100%;"></p><!--[if mso | IE]><table align="center" border="0" cellpadding="0" cellspacing="0" style="border-top:solid 2px #cccccc;font-size:1;margin:0px auto;width:510px;" role="presentation" width="510px" ><tr><td style="height:0;line-height:0;"> &nbsp;
</td></tr></table><![endif]--></td></tr></table></div><!--[if mso | IE]></td></tr></table><![endif]--></td></tr></tbody></table></div><!--[if mso | IE]></td></tr></table><![endif]--></div></body></html>
</td></tr></table><![endif]--></td></tr></table></div><!--[if mso | IE]></td></tr></table><![endif]--></td></tr></tbody></table></div><!--[if mso | IE]></td></tr></table><![endif]--></div></body></html>

12
backend/app/tests/scripts/test_backend_pre_start.py

@ -24,10 +24,10 @@ def test_init_successful_connection() -> None:
except Exception:
connection_successful = False
assert (
connection_successful
), "The database connection should be successful and not raise an exception."
assert connection_successful, (
"The database connection should be successful and not raise an exception."
)
assert session_mock.exec.called_once_with(
select(1)
), "The session should execute a select statement once."
assert session_mock.exec.called_once_with(select(1)), (
"The session should execute a select statement once."
)

12
backend/app/tests/scripts/test_test_pre_start.py

@ -24,10 +24,10 @@ def test_init_successful_connection() -> None:
except Exception:
connection_successful = False
assert (
connection_successful
), "The database connection should be successful and not raise an exception."
assert connection_successful, (
"The database connection should be successful and not raise an exception."
)
assert session_mock.exec.called_once_with(
select(1)
), "The session should execute a select statement once."
assert session_mock.exec.called_once_with(select(1)), (
"The session should execute a select statement once."
)

2
development.md

@ -204,4 +204,4 @@ Adminer: http://localhost.tiangolo.com:8080
Traefik UI: http://localhost.tiangolo.com:8090
MailCatcher: http://localhost.tiangolo.com:1080
MailCatcher: http://localhost.tiangolo.com:1080

2
frontend/.gitignore

@ -27,4 +27,4 @@ openapi.json
/playwright-report/
/blob-report/
/playwright/.cache/
/playwright/.auth/
/playwright/.auth/

2
frontend/src/components/Pending/PendingItems.tsx

@ -4,7 +4,7 @@ import { SkeletonText } from "../ui/skeleton"
const PendingItems = () => {
const { t } = useTranslation()
return (
<Table.Root size={{ base: "sm", md: "md" }}>
<Table.Header>

2
frontend/src/components/UserSettings/DeleteAccount.tsx

@ -5,7 +5,7 @@ import DeleteConfirmation from "./DeleteConfirmation"
const DeleteAccount = () => {
const { t } = useTranslation()
return (
<Container maxW="full">
<Heading size="sm" py={4}>

4
frontend/src/components/ui/password-input.tsx

@ -97,7 +97,7 @@ export const PasswordInput = forwardRef<HTMLInputElement, PasswordInputProps>(
const VisibilityTrigger = forwardRef<HTMLButtonElement, ButtonProps>(
function VisibilityTrigger(props, ref) {
const { t } = useTranslation()
return (
<IconButton
tabIndex={-1}
@ -155,7 +155,7 @@ export const PasswordStrengthMeter = forwardRef<
function getColorPalette(percent: number) {
const { t } = useTranslation()
switch (true) {
case percent < 33:
return { label: t("auth.passwordStrengthLow"), colorPalette: "red" }

2
frontend/src/hooks/useCustomToast.ts

@ -5,7 +5,7 @@ import { toaster } from "@/components/ui/toaster"
const useCustomToast = () => {
const { t } = useTranslation()
const showSuccessToast = (description: string) => {
toaster.create({
title: t("common.success"),

6
frontend/src/i18n/index.ts

@ -21,15 +21,15 @@ i18n
resources,
fallbackLng: 'en',
debug: false,
interpolation: {
escapeValue: false
},
detection: {
order: ['localStorage', 'navigator', 'htmlTag'],
caches: ['localStorage']
}
})
export default i18n
export default i18n

2
frontend/src/routes/_layout/admin.tsx

@ -117,7 +117,7 @@ function UsersTable() {
function Admin() {
const { t } = useTranslation()
return (
<Container maxW="full">
<Heading size="lg" pt={12}>

2
frontend/src/routes/_layout/items.tsx

@ -135,7 +135,7 @@ function ItemsTable() {
function Items() {
const { t } = useTranslation()
return (
<Container maxW="full">
<Heading size="lg" pt={12}>

4
frontend/src/routes/_layout/settings.tsx

@ -11,14 +11,14 @@ import useAuth from "@/hooks/useAuth"
function UserSettings() {
const { user: currentUser } = useAuth()
const { t } = useTranslation()
const tabsConfig = [
{ value: "my-profile", title: t("user.profile"), component: UserInformation },
{ value: "password", title: t("user.changePassword"), component: ChangePassword },
{ value: "appearance", title: t("user.appearance"), component: Appearance },
{ value: "danger-zone", title: t("user.deleteAccount"), component: DeleteAccount },
]
const finalTabs = currentUser?.is_superuser
? tabsConfig.slice(0, 3)
: tabsConfig

1
hooks/post_gen_project.py

@ -1,6 +1,5 @@
from pathlib import Path
path: Path
for path in Path(".").glob("**/*.sh"):
data = path.read_bytes()

Loading…
Cancel
Save