From d16e66807d29a192476e1c56e657d440a0e8f58b Mon Sep 17 00:00:00 2001 From: qzl Date: Wed, 25 Feb 2026 10:32:19 +0800 Subject: [PATCH] chore: lock runtime log output paths and ignore local logs --- .gitignore | 2 + backend/src/core/config/settings.py | 22 ++- backend/tests/unit/test_logging_settings.py | 8 +- .../2026-02-24-auth-profile-implementation.md | 136 ++++++++++++++++++ 4 files changed, 165 insertions(+), 3 deletions(-) create mode 100644 docs/plans/2026-02-24-auth-profile-implementation.md diff --git a/.gitignore b/.gitignore index 45378d3..656bc15 100644 --- a/.gitignore +++ b/.gitignore @@ -285,8 +285,10 @@ infra/cloud/volcano/env/*.env *.swp .buildlog/ .history +/logs/ # Docker volumes (local data) docker/supabase/volumes/db/data/ +infra/docker/volumes/db/data/ # OpenCode local config .opencode/ diff --git a/backend/src/core/config/settings.py b/backend/src/core/config/settings.py index 568ea21..58f57ae 100644 --- a/backend/src/core/config/settings.py +++ b/backend/src/core/config/settings.py @@ -4,7 +4,7 @@ from pathlib import Path from typing import ClassVar, Literal from urllib.parse import quote -from pydantic import BaseModel, Field, computed_field +from pydantic import BaseModel, Field, computed_field, field_validator from pydantic_settings import BaseSettings, SettingsConfigDict @@ -37,6 +37,26 @@ class RuntimeSettings(BaseModel): ) sql_log_queries: bool = False + @field_validator("log_dir", mode="before") + @classmethod + def lock_log_dir(cls, _: object) -> str: + return "logs" + + @field_validator("log_error_dir", mode="before") + @classmethod + def lock_log_error_dir(cls, _: object) -> str: + return "logs/errors" + + @field_validator("log_file_name", mode="before") + @classmethod + def lock_log_file_name(cls, _: object) -> str: + return "app.log" + + @field_validator("log_error_file_name", mode="before") + @classmethod + def lock_log_error_file_name(cls, _: object) -> str: + return "error.log" + class CelerySettings(BaseModel): broker_url: str | None = None diff --git a/backend/tests/unit/test_logging_settings.py b/backend/tests/unit/test_logging_settings.py index 834c622..62447de 100644 --- a/backend/tests/unit/test_logging_settings.py +++ b/backend/tests/unit/test_logging_settings.py @@ -24,12 +24,16 @@ def test_runtime_settings_defaults() -> None: def test_runtime_settings_env_override(monkeypatch: MonkeyPatch) -> None: monkeypatch.setenv("SOCIAL_RUNTIME__LOG_DIR", "var/logs") monkeypatch.setenv("SOCIAL_RUNTIME__LOG_ERROR_DIR", "var/logs/errors") + monkeypatch.setenv("SOCIAL_RUNTIME__LOG_FILE_NAME", "custom.log") + monkeypatch.setenv("SOCIAL_RUNTIME__LOG_ERROR_FILE_NAME", "custom-error.log") monkeypatch.setenv("SOCIAL_RUNTIME__LOG_ROTATION", "size") monkeypatch.setenv("SOCIAL_RUNTIME__LOG_ROTATION_MAX_BYTES", "2048") settings = Settings() - assert settings.runtime.log_dir == "var/logs" - assert settings.runtime.log_error_dir == "var/logs/errors" + assert settings.runtime.log_dir == "logs" + assert settings.runtime.log_error_dir == "logs/errors" + assert settings.runtime.log_file_name == "app.log" + assert settings.runtime.log_error_file_name == "error.log" assert settings.runtime.log_rotation == "size" assert settings.runtime.log_rotation_max_bytes == 2048 diff --git a/docs/plans/2026-02-24-auth-profile-implementation.md b/docs/plans/2026-02-24-auth-profile-implementation.md new file mode 100644 index 0000000..1f82296 --- /dev/null +++ b/docs/plans/2026-02-24-auth-profile-implementation.md @@ -0,0 +1,136 @@ +# Auth Profile Enhancement Implementation Plan + +> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. + +**Goal:** 基于 Supabase 能力补齐注册/登录/按邮箱查用户/更新 profile 的一致化实现,并移除 profiles.display_name。 + +**Architecture:** 认证流程继续走 Supabase Auth(signup/login/refresh/logout),后端仅做薄封装与输入校验。profiles 通过 auth.users 触发器自动创建并绑定同一 id,profile 资料更新仍走业务接口。新增按邮箱查用户接口走 service_role 的 Admin API。 + +**Tech Stack:** FastAPI, Supabase Python SDK, SQLAlchemy, Alembic, PostgreSQL + +--- + +### Task 1: 调整 profiles 数据模型与迁移 + +**Files:** +- Modify: `backend/src/models/profile.py` +- Create: `backend/alembic/versions/20260224_drop_profile_display_name_and_trigger_username.py` + +**Step 1: Write the failing test** + +- 新增迁移验证脚本测试:断言 `profiles` 不含 `display_name` 且触发器使用 metadata.username。 + +**Step 2: Run test to verify it fails** + +Run: `PYTHONPATH=src uv run python -m pytest tests/integration -k profile_migration -q` +Expected: FAIL(旧结构仍有 display_name 或触发器逻辑不匹配) + +**Step 3: Write minimal implementation** + +- model 删除 `display_name` +- 迁移删除列并重建触发器函数:`profiles.username = NEW.raw_user_meta_data->>'username'` + +**Step 4: Run test to verify it passes** + +Run: `PYTHONPATH=src uv run python -m pytest tests/integration -k profile_migration -q` +Expected: PASS + +### Task 2: 注册接口改为 username+email+password + +**Files:** +- Modify: `backend/src/v1/auth/schemas.py` +- Modify: `backend/src/v1/auth/service.py` + +**Step 1: Write the failing test** + +- 测试 signup 缺 username 返回 422 +- 测试 signup 将 `data.username` 传递给 Supabase gateway + +**Step 2: Run test to verify it fails** + +Run: `PYTHONPATH=src uv run python -m pytest tests/unit -k auth_signup -q` +Expected: FAIL + +**Step 3: Write minimal implementation** + +- `SignupRequest` 添加必填 `username` +- `SupabaseAuthGateway.signup` payload 增加 `data.username` + +**Step 4: Run test to verify it passes** + +Run: `PYTHONPATH=src uv run python -m pytest tests/unit -k auth_signup -q` +Expected: PASS + +### Task 3: 新增按邮箱查询 auth 用户接口 + +**Files:** +- Modify: `backend/src/v1/auth/router.py` +- Modify: `backend/src/v1/auth/service.py` +- Modify: `backend/src/v1/auth/schemas.py` + +**Step 1: Write the failing test** + +- 测试 `GET /auth/users/by-email` 命中返回用户最小字段 +- 测试未命中返回 404 + +**Step 2: Run test to verify it fails** + +Run: `PYTHONPATH=src uv run python -m pytest tests/unit -k auth_by_email -q` +Expected: FAIL + +**Step 3: Write minimal implementation** + +- service 新增 `get_user_by_email` +- gateway 用 service_role client 调用 Supabase Admin 查询 +- router 暴露 `GET /auth/users/by-email` + +**Step 4: Run test to verify it passes** + +Run: `PYTHONPATH=src uv run python -m pytest tests/unit -k auth_by_email -q` +Expected: PASS + +### Task 4: profile 更新协议去除 display_name + +**Files:** +- Modify: `backend/src/v1/profile/schemas.py` +- Modify: `backend/src/v1/profile/service.py` +- Modify: `backend/src/v1/profile/router.py` + +**Step 1: Write the failing test** + +- 测试 `PATCH /profile/me` 仅允许 `username/avatar_url/bio` + +**Step 2: Run test to verify it fails** + +Run: `PYTHONPATH=src uv run python -m pytest tests/unit -k profile_update -q` +Expected: FAIL + +**Step 3: Write minimal implementation** + +- 移除 display_name 字段与映射 +- 保留原有更新路径和事务边界 + +**Step 4: Run test to verify it passes** + +Run: `PYTHONPATH=src uv run python -m pytest tests/unit -k profile_update -q` +Expected: PASS + +### Task 5: 集成验证 + +**Files:** +- Modify: `docs/runtime/runtime-runbook.md` + +**Step 1: 运行关键验证** + +Run: `docker compose --env-file .env -f infra/docker/docker-compose.yml --profile job run --rm init-job` +Expected: 迁移成功 + +Run: `PYTHONPATH=src uv run python -m pytest tests -q` +Expected: 全部通过 + +**Step 2: 手工 API 验证** + +- signup(username,email,password) 成功 +- login(email,password) 成功 +- by-email 查询命中 +- patch profile 更新成功