Skip to content

Commit 2cb47d3

Browse files
committed
♻️ refactor(backend): 重构用户和模块相关代码结构
- 将用户模型和CRUD操作迁移到service层,实现逻辑分离 - 统一使用UserService处理用户认证、密码管理等业务逻辑 - 优化模块导入顺序并清理多余的空格,保持代码风格统一 - 调整模型类文件结构,分离公共模型定义与数据库模型
1 parent a7f84be commit 2cb47d3

File tree

10 files changed

+153
-53
lines changed

10 files changed

+153
-53
lines changed

backend/app/api/deps.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1+
import uuid
12
from collections.abc import Generator
23
from typing import Annotated
34

4-
import uuid
5+
56
import jwt
67
from fastapi import Depends, HTTPException, status
78
from fastapi.security import OAuth2PasswordBearer
@@ -12,8 +13,9 @@
1213
from app.core import security
1314
from app.core.config import settings
1415
from app.core.db import engine
15-
from app.models import TokenPayload
1616
from app.model.users import User
17+
from app.models import TokenPayload
18+
1719

1820
reusable_oauth2 = OAuth2PasswordBearer(
1921
tokenUrl=f"{settings.API_V1_STR}/login/access-token"

backend/app/api/routes/login.py

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,14 @@
55
from fastapi.responses import HTMLResponse
66
from fastapi.security import OAuth2PasswordRequestForm
77

8-
from app import crud
98
from app.api.deps import CurrentUser, SessionDep, get_current_active_superuser
109
from app.core import security
1110
from app.core.config import settings
1211
from app.core.security import get_password_hash
12+
from app.model.users import UserPublic
1313
from app.models import Message, NewPassword, Token
1414
from app.model.users import UserPublic
15+
from app.service.user_service import UserService
1516
from app.utils import (
1617
generate_password_reset_token,
1718
generate_reset_password_email,
@@ -29,8 +30,9 @@ def login_access_token(
2930
"""
3031
OAuth2 compatible token login, get an access token for future requests
3132
"""
32-
user = crud.authenticate(
33-
session=session, email=form_data.username, password=form_data.password
33+
user_service = UserService(session)
34+
user = user_service.authenticate(
35+
email=form_data.username, password=form_data.password
3436
)
3537
if not user:
3638
raise HTTPException(status_code=400, detail="Incorrect email or password")
@@ -57,7 +59,8 @@ def recover_password(email: str, session: SessionDep) -> Message:
5759
"""
5860
Password Recovery
5961
"""
60-
user = crud.get_user_by_email(session=session, email=email)
62+
user_service = UserService(session)
63+
user = user_service.get_user_by_email(email=email)
6164

6265
if not user:
6366
raise HTTPException(
@@ -84,7 +87,8 @@ def reset_password(session: SessionDep, body: NewPassword) -> Message:
8487
email = verify_password_reset_token(token=body.token)
8588
if not email:
8689
raise HTTPException(status_code=400, detail="Invalid token")
87-
user = crud.get_user_by_email(session=session, email=email)
90+
user_service = UserService(session)
91+
user = user_service.get_user_by_email(email=email)
8892
if not user:
8993
raise HTTPException(
9094
status_code=404,
@@ -108,7 +112,8 @@ def recover_password_html_content(email: str, session: SessionDep) -> Any:
108112
"""
109113
HTML Content for Password Recovery
110114
"""
111-
user = crud.get_user_by_email(session=session, email=email)
115+
user_service = UserService(session)
116+
user = user_service.get_user_by_email(email=email)
112117

113118
if not user:
114119
raise HTTPException(

backend/app/api/routes/private.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,7 @@
55

66
from app.api.deps import SessionDep
77
from app.core.security import get_password_hash
8-
from app.models import (
9-
User,
10-
UserPublic,
11-
)
8+
from app.model.users import User, UserPublic
129

1310
router = APIRouter(tags=["private"], prefix="/private")
1411

backend/app/api/routes/users.py

Lines changed: 8 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,29 +2,25 @@
22
from typing import Any
33

44
from fastapi import APIRouter, Depends, HTTPException
5-
from sqlmodel import col, delete, func, select
65

7-
from app.service.user_service import UserService
86
from app.api.deps import (
97
CurrentUser,
108
SessionDep,
119
get_current_active_superuser,
1210
)
1311
from app.core.config import settings
14-
from app.core.security import get_password_hash, verify_password
15-
from app.models import Message
16-
from app.utils import generate_new_account_email, send_email
1712
from app.model.users import (
1813
UpdatePassword,
19-
User,
2014
UserCreate,
2115
UserPublic,
2216
UserRegister,
17+
UsersPublic,
2318
UserUpdate,
2419
UserUpdateMe,
25-
UsersPublic,
2620
)
27-
from app.model.items import Item
21+
from app.models import Message
22+
from app.service.user_service import UserService
23+
from app.utils import generate_new_account_email, send_email
2824

2925
router = APIRouter(prefix="/users", tags=["users"])
3026

@@ -95,14 +91,13 @@ def update_password_me(
9591
user_service = UserService(session)
9692
try:
9793
user_service.update_password(
98-
current_user,
99-
body.current_password,
100-
body.new_password
94+
current_user, body.current_password, body.new_password
10195
)
10296
return Message(message="Password updated successfully")
10397
except ValueError as e:
10498
raise HTTPException(status_code=400, detail=str(e))
10599

100+
106101
@router.get("/me", response_model=UserPublic)
107102
def read_user_me(current_user: CurrentUser) -> Any:
108103
"""
@@ -116,13 +111,10 @@ def delete_user_me(session: SessionDep, current_user: CurrentUser) -> Any:
116111
"""
117112
Delete own user.
118113
"""
119-
120114
user_service = UserService(session)
121115
try:
122116
user_service.delete_user(
123-
str(current_user.id),
124-
str(current_user.id),
125-
current_user.is_superuser
117+
str(current_user.id), str(current_user.id), current_user.is_superuser
126118
)
127119
return Message(message="User deleted successfully")
128120
except ValueError as e:
@@ -201,9 +193,7 @@ def delete_user(
201193
user_service = UserService(session)
202194
try:
203195
user_service.delete_user(
204-
str(user_id),
205-
str(current_user.id),
206-
current_user.is_superuser
196+
str(user_id), str(current_user.id), current_user.is_superuser
207197
)
208198
return Message(message="User deleted successfully")
209199
except ValueError as e:

backend/app/core/db.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
from sqlmodel import Session, create_engine, select
22

3-
from app import crud
3+
44
from app.core.config import settings
5-
from app.models import User, UserCreate
5+
from app.model.users import User, UserCreate
6+
from app.service.user_service import UserService
67

78
engine = create_engine(str(settings.SQLALCHEMY_DATABASE_URI))
89

@@ -30,4 +31,5 @@ def init_db(session: Session) -> None:
3031
password=settings.FIRST_SUPERUSER_PASSWORD,
3132
is_superuser=True,
3233
)
33-
user = crud.create_user(session=session, user_create=user_in)
34+
user_service = UserService(session)
35+
user = user_service.create_user(user_in)

backend/app/model/users.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
import uuid
2-
from typing import Any, Optional
2+
from typing import Any
33

44
from pydantic import EmailStr
5-
from sqlmodel import Field, Relationship, SQLModel, Session, select, func
5+
from sqlmodel import Field, Relationship, Session, SQLModel, func, select
66

7-
from app.core.security import get_password_hash, verify_password
7+
from app.core.security import get_password_hash
8+
from app.model.items import Item
89

910

1011
class UserBase(SQLModel):
@@ -42,12 +43,13 @@ class UpdatePassword(SQLModel):
4243
class User(UserBase, table=True):
4344
id: uuid.UUID = Field(default_factory=uuid.uuid4, primary_key=True)
4445
hashed_password: str
45-
items: list["Item"] = Relationship(back_populates="owner", cascade_delete=True) # type: ignore
46+
items: list["Item"] = Relationship(back_populates="owner", cascade_delete=True)
4647

4748
@classmethod
4849
def create(cls, session: Session, user_create: UserCreate) -> "User":
4950
db_obj = cls.model_validate(
50-
user_create, update={"hashed_password": get_password_hash(user_create.password)}
51+
user_create,
52+
update={"hashed_password": get_password_hash(user_create.password)},
5153
)
5254
session.add(db_obj)
5355
session.commit()

backend/app/models.py

Lines changed: 93 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,96 @@
1-
from sqlmodel import SQLModel, Field
1+
import uuid
2+
3+
from pydantic import EmailStr
4+
from sqlmodel import Field, Relationship, SQLModel
5+
6+
7+
# Shared properties
8+
class UserBase(SQLModel):
9+
email: EmailStr = Field(unique=True, index=True, max_length=255)
10+
is_active: bool = True
11+
is_superuser: bool = False
12+
full_name: str | None = Field(default=None, max_length=255)
13+
14+
15+
# Properties to receive via API on creation
16+
class UserCreate(UserBase):
17+
password: str = Field(min_length=8, max_length=40)
18+
19+
20+
class UserRegister(SQLModel):
21+
email: EmailStr = Field(max_length=255)
22+
password: str = Field(min_length=8, max_length=40)
23+
full_name: str | None = Field(default=None, max_length=255)
24+
25+
26+
# Properties to receive via API on update, all are optional
27+
class UserUpdate(UserBase):
28+
email: EmailStr | None = Field(default=None, max_length=255) # type: ignore
29+
password: str | None = Field(default=None, min_length=8, max_length=40)
30+
31+
32+
class UserUpdateMe(SQLModel):
33+
full_name: str | None = Field(default=None, max_length=255)
34+
email: EmailStr | None = Field(default=None, max_length=255)
35+
36+
37+
class UpdatePassword(SQLModel):
38+
current_password: str = Field(min_length=8, max_length=40)
39+
new_password: str = Field(min_length=8, max_length=40)
40+
41+
42+
# Database model, database table inferred from class name
43+
class User(UserBase, table=True):
44+
id: uuid.UUID = Field(default_factory=uuid.uuid4, primary_key=True)
45+
hashed_password: str
46+
items: list["Item"] = Relationship(back_populates="owner", cascade_delete=True)
47+
48+
49+
# Properties to return via API, id is always required
50+
class UserPublic(UserBase):
51+
id: uuid.UUID
52+
53+
54+
class UsersPublic(SQLModel):
55+
data: list[UserPublic]
56+
count: int
57+
58+
59+
# Shared properties
60+
class ItemBase(SQLModel):
61+
title: str = Field(min_length=1, max_length=255)
62+
description: str | None = Field(default=None, max_length=255)
63+
64+
65+
# Properties to receive on item creation
66+
class ItemCreate(ItemBase):
67+
pass
68+
69+
70+
# Properties to receive on item update
71+
class ItemUpdate(ItemBase):
72+
title: str | None = Field(default=None, min_length=1, max_length=255) # type: ignore
73+
74+
75+
# Database model, database table inferred from class name
76+
class Item(ItemBase, table=True):
77+
id: uuid.UUID = Field(default_factory=uuid.uuid4, primary_key=True)
78+
title: str = Field(max_length=255)
79+
owner_id: uuid.UUID = Field(
80+
foreign_key="user.id", nullable=False, ondelete="CASCADE"
81+
)
82+
owner: User | None = Relationship(back_populates="items")
83+
84+
85+
# Properties to return via API, id is always required
86+
class ItemPublic(ItemBase):
87+
id: uuid.UUID
88+
owner_id: uuid.UUID
89+
90+
91+
class ItemsPublic(SQLModel):
92+
data: list[ItemPublic]
93+
count: int
294

395

496
# Generic message

backend/app/service/item_service.py

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import uuid
22
from typing import Any
33

4-
from sqlmodel import Session
54
from fastapi import HTTPException
5+
from sqlmodel import Session
66

77
from app.model.items import Item, ItemCreate, ItemUpdate
88
from app.models import Message
@@ -22,10 +22,13 @@ def read_items(
2222
owner_id=owner_id,
2323
is_superuser=is_superuser,
2424
skip=skip,
25-
limit=limit
25+
limit=limit,
2626
)
2727

28-
def read_item(self, item_id: uuid.UUID, owner_id: uuid.UUID, is_superuser: bool) -> Any:
28+
29+
def read_item(
30+
self, item_id: uuid.UUID, owner_id: uuid.UUID, is_superuser: bool
31+
) -> Any:
2932
item = Item.get_by_id(self.session, item_id)
3033
if not item:
3134
raise HTTPException(status_code=404, detail="Item not found")
@@ -34,7 +37,11 @@ def read_item(self, item_id: uuid.UUID, owner_id: uuid.UUID, is_superuser: bool)
3437
return item
3538

3639
def update_item(
37-
self, item_id: uuid.UUID, item_in: ItemUpdate, owner_id: uuid.UUID, is_superuser: bool
40+
self,
41+
item_id: uuid.UUID,
42+
item_in: ItemUpdate,
43+
owner_id: uuid.UUID,
44+
is_superuser: bool,
3845
) -> Any:
3946
item = Item.get_by_id(self.session, item_id)
4047
if not item:
@@ -43,7 +50,9 @@ def update_item(
4350
raise HTTPException(status_code=400, detail="Not enough permissions")
4451
return Item.update(self.session, item, item_in)
4552

46-
def delete_item(self, item_id: uuid.UUID, owner_id: uuid.UUID, is_superuser: bool) -> Message:
53+
def delete_item(
54+
self, item_id: uuid.UUID, owner_id: uuid.UUID, is_superuser: bool
55+
) -> Message:
4756
item = Item.get_by_id(self.session, item_id)
4857
if not item:
4958
raise HTTPException(status_code=404, detail="Item not found")

0 commit comments

Comments
 (0)