Skip to content

Commit 821d83b

Browse files
committed
🐛 fix(config): 修复超级用户邮箱格式问题
- 将FIRST_SUPERUSER改为符合标准的邮箱格式[email protected] ♻️ refactor(models): 重构数据模型结构 - 将用户和条目模型拆分到独立model子目录 - 更新所有相关模块的模型导入路径 - 清理原models.py文件冗余代码 📦 build(migrations): 添加数据库迁移脚本 - 新增user和item表的初始化alembic迁移文件 - 包含升级和回滚操作的完整迁移逻辑
1 parent e7a524d commit 821d83b

File tree

11 files changed

+155
-99
lines changed

11 files changed

+155
-99
lines changed

.env

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ STACK_NAME=full-stack-fastapi-project
1919
# Backend
2020
BACKEND_CORS_ORIGINS="http://localhost,http://localhost:5173,https://localhost,https://localhost:5173,http://localhost.tiangolo.com"
2121
SECRET_KEY=UXjzVJ3dRZEDKD8RkdUBTBsSPVETMj3R
22-
FIRST_SUPERUSER=admin
22+
FIRST_SUPERUSER=admin@example.com
2323
FIRST_SUPERUSER_PASSWORD=Torghay1988
2424

2525
# Emails
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
"""Upgrade model
2+
3+
Revision ID: 324024cfba63
4+
Revises:
5+
Create Date: 2025-01-14 14:35:35.495095
6+
7+
"""
8+
from alembic import op
9+
import sqlalchemy as sa
10+
import sqlmodel.sql.sqltypes
11+
12+
13+
# revision identifiers, used by Alembic.
14+
revision = '324024cfba63'
15+
down_revision = None
16+
branch_labels = None
17+
depends_on = None
18+
19+
20+
def upgrade():
21+
# ### commands auto generated by Alembic - please adjust! ###
22+
op.create_table('user',
23+
sa.Column('email', sqlmodel.sql.sqltypes.AutoString(length=255), nullable=False),
24+
sa.Column('is_active', sa.Boolean(), nullable=False),
25+
sa.Column('is_superuser', sa.Boolean(), nullable=False),
26+
sa.Column('full_name', sqlmodel.sql.sqltypes.AutoString(length=255), nullable=True),
27+
sa.Column('id', sa.Uuid(), nullable=False),
28+
sa.Column('hashed_password', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
29+
sa.PrimaryKeyConstraint('id')
30+
)
31+
op.create_index(op.f('ix_user_email'), 'user', ['email'], unique=True)
32+
op.create_table('item',
33+
sa.Column('description', sqlmodel.sql.sqltypes.AutoString(length=255), nullable=True),
34+
sa.Column('id', sa.Uuid(), nullable=False),
35+
sa.Column('title', sqlmodel.sql.sqltypes.AutoString(length=255), nullable=False),
36+
sa.Column('owner_id', sa.Uuid(), nullable=False),
37+
sa.ForeignKeyConstraint(['owner_id'], ['user.id'], ondelete='CASCADE'),
38+
sa.PrimaryKeyConstraint('id')
39+
)
40+
# ### end Alembic commands ###
41+
42+
43+
def downgrade():
44+
# ### commands auto generated by Alembic - please adjust! ###
45+
op.drop_table('item')
46+
op.drop_index(op.f('ix_user_email'), table_name='user')
47+
op.drop_table('user')
48+
# ### end Alembic commands ###

backend/app/api/deps.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@
1212
from app.core import security
1313
from app.core.config import settings
1414
from app.core.db import engine
15-
from app.models import TokenPayload, User
15+
from app.models import TokenPayload
16+
from app.model.users import User
1617

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

backend/app/api/routes/items.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@
55
from sqlmodel import func, select
66

77
from app.api.deps import CurrentUser, SessionDep
8-
from app.models import Item, ItemCreate, ItemPublic, ItemsPublic, ItemUpdate, Message
8+
from app.models import TokenPayload
9+
from app.model.users import User
910

1011
router = APIRouter(prefix="/items", tags=["items"])
1112

backend/app/api/routes/login.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@
1010
from app.core import security
1111
from app.core.config import settings
1212
from app.core.security import get_password_hash
13-
from app.models import Message, NewPassword, Token, UserPublic
13+
from app.models import Message, NewPassword, Token
14+
from app.model.users import UserPublic
1415
from app.utils import (
1516
generate_password_reset_token,
1617
generate_reset_password_email,

backend/app/model/__init__.py

Whitespace-only changes.

backend/app/model/items.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import uuid
2+
3+
from app.model.users import User
4+
from sqlmodel import Field, Relationship, SQLModel
5+
6+
7+
# Shared properties
8+
class ItemBase(SQLModel):
9+
title: str = Field(min_length=1, max_length=255)
10+
description: str | None = Field(default=None, max_length=255)
11+
12+
13+
# Properties to receive on item creation
14+
class ItemCreate(ItemBase):
15+
pass
16+
17+
18+
# Properties to receive on item update
19+
class ItemUpdate(ItemBase):
20+
title: str | None = Field(default=None, min_length=1, max_length=255) # type: ignore
21+
22+
23+
# Database model, database table inferred from class name
24+
class Item(ItemBase, table=True):
25+
id: uuid.UUID = Field(default_factory=uuid.uuid4, primary_key=True)
26+
title: str = Field(max_length=255)
27+
owner_id: uuid.UUID = Field(
28+
foreign_key="user.id", nullable=False, ondelete="CASCADE"
29+
)
30+
owner: User | None = Relationship(back_populates="items")
31+
32+
33+
# Properties to return via API, id is always required
34+
class ItemPublic(ItemBase):
35+
id: uuid.UUID
36+
owner_id: uuid.UUID
37+
38+
39+
class ItemsPublic(SQLModel):
40+
data: list[ItemPublic]
41+
count: int

backend/app/model/users.py

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
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) # type: ignore
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

backend/app/models.py

Lines changed: 1 addition & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -1,96 +1,4 @@
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
1+
from sqlmodel import SQLModel, Field
942

953

964
# Generic message

backend/app/tests/api/routes/test_login.py

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

66
from app.core.config import settings
77
from app.core.security import verify_password
8-
from app.models import User
8+
from app.model.users import User
99
from app.utils import generate_password_reset_token
1010

1111

0 commit comments

Comments
 (0)