Spaces:
Sleeping
Sleeping
Praneeth Yerrapragada
commited on
Commit
•
d4e18c8
1
Parent(s):
4192f93
fix: fix users endpoints
Browse files- app/api/routers/user.py +27 -23
- app/model/base.py +9 -4
- app/model/user.py +22 -26
- app/schema/base.py +5 -1
- app/schema/index.py +16 -3
- migration/versions/7ea44cbc5b1f_default_timezone.py +69 -0
app/api/routers/user.py
CHANGED
@@ -3,7 +3,7 @@ from fastapi import APIRouter, Depends, HTTPException
|
|
3 |
from sqlalchemy.ext.asyncio import AsyncSession
|
4 |
|
5 |
from app.engine.postgresdb import get_db_session
|
6 |
-
from app.schema.index import UserCreate, User as UserSchema
|
7 |
from app.model.user import User as UserModel
|
8 |
|
9 |
|
@@ -13,7 +13,7 @@ logger = logging.getLogger(__name__)
|
|
13 |
|
14 |
@r.post(
|
15 |
"/",
|
16 |
-
response_model=
|
17 |
responses={
|
18 |
200: {"description": "New user created"},
|
19 |
400: {"description": "Bad request"},
|
@@ -23,63 +23,67 @@ logger = logging.getLogger(__name__)
|
|
23 |
)
|
24 |
async def create_user(user: UserCreate, db: AsyncSession = Depends(get_db_session)):
|
25 |
try:
|
26 |
-
logger.info(f"Checking if user exists: {user.dict()}")
|
27 |
db_user = await UserModel.get(db, email=user.email)
|
28 |
if db_user and not db_user.is_deleted:
|
29 |
raise HTTPException(status_code=409, detail="User already exists")
|
30 |
|
31 |
-
|
32 |
-
|
33 |
-
return
|
34 |
except Exception as e:
|
35 |
raise HTTPException(status_code=500, detail=str(e))
|
36 |
|
37 |
|
38 |
@r.get(
|
39 |
-
"/{
|
40 |
-
response_model=
|
41 |
responses={
|
42 |
200: {"description": "User found"},
|
43 |
404: {"description": "User not found"},
|
44 |
500: {"description": "Internal server error"},
|
45 |
},
|
46 |
)
|
47 |
-
async def get_user(
|
48 |
-
user = await UserModel.get(db,
|
49 |
if not user:
|
50 |
raise HTTPException(status_code=404, detail="User not found")
|
51 |
return user
|
52 |
|
53 |
|
54 |
@r.put(
|
55 |
-
"/{
|
56 |
-
response_model=
|
57 |
responses={
|
58 |
200: {"description": "User updated"},
|
59 |
404: {"description": "User not found"},
|
60 |
500: {"description": "Internal server error"},
|
61 |
},
|
62 |
)
|
63 |
-
async def update_user(
|
64 |
-
|
65 |
-
|
66 |
-
|
67 |
-
|
68 |
-
|
|
|
|
|
|
|
|
|
69 |
|
70 |
|
71 |
@r.delete(
|
72 |
-
"/{
|
73 |
-
response_model=
|
74 |
responses={
|
75 |
200: {"description": "User deleted"},
|
76 |
404: {"description": "User not found"},
|
77 |
500: {"description": "Internal server error"},
|
78 |
},
|
79 |
)
|
80 |
-
async def delete_user(
|
81 |
-
user = await UserModel.get(db,
|
82 |
if not user:
|
83 |
raise HTTPException(status_code=404, detail="User not found")
|
84 |
-
|
|
|
85 |
return user
|
|
|
3 |
from sqlalchemy.ext.asyncio import AsyncSession
|
4 |
|
5 |
from app.engine.postgresdb import get_db_session
|
6 |
+
from app.schema.index import UserCreate, User as UserSchema, UserResponse, UserUpdate
|
7 |
from app.model.user import User as UserModel
|
8 |
|
9 |
|
|
|
13 |
|
14 |
@r.post(
|
15 |
"/",
|
16 |
+
response_model=UserResponse,
|
17 |
responses={
|
18 |
200: {"description": "New user created"},
|
19 |
400: {"description": "Bad request"},
|
|
|
23 |
)
|
24 |
async def create_user(user: UserCreate, db: AsyncSession = Depends(get_db_session)):
|
25 |
try:
|
|
|
26 |
db_user = await UserModel.get(db, email=user.email)
|
27 |
if db_user and not db_user.is_deleted:
|
28 |
raise HTTPException(status_code=409, detail="User already exists")
|
29 |
|
30 |
+
await UserModel.create(db, **user.dict())
|
31 |
+
user = await UserModel.get(db, email=user.email)
|
32 |
+
return user
|
33 |
except Exception as e:
|
34 |
raise HTTPException(status_code=500, detail=str(e))
|
35 |
|
36 |
|
37 |
@r.get(
|
38 |
+
"/{email}",
|
39 |
+
response_model=UserResponse,
|
40 |
responses={
|
41 |
200: {"description": "User found"},
|
42 |
404: {"description": "User not found"},
|
43 |
500: {"description": "Internal server error"},
|
44 |
},
|
45 |
)
|
46 |
+
async def get_user(email: str, db: AsyncSession = Depends(get_db_session)):
|
47 |
+
user = await UserModel.get(db, email=email)
|
48 |
if not user:
|
49 |
raise HTTPException(status_code=404, detail="User not found")
|
50 |
return user
|
51 |
|
52 |
|
53 |
@r.put(
|
54 |
+
"/{email}",
|
55 |
+
response_model=UserResponse,
|
56 |
responses={
|
57 |
200: {"description": "User updated"},
|
58 |
404: {"description": "User not found"},
|
59 |
500: {"description": "Internal server error"},
|
60 |
},
|
61 |
)
|
62 |
+
async def update_user(email: str, user_payload: UserUpdate, db: AsyncSession = Depends(get_db_session)):
|
63 |
+
try:
|
64 |
+
user = await UserModel.get(db, email=email)
|
65 |
+
if not user:
|
66 |
+
raise HTTPException(status_code=404, detail="User not found")
|
67 |
+
await UserModel.update(db, id=user.id, **user_payload.dict())
|
68 |
+
user = await UserModel.get(db, email=email)
|
69 |
+
return user
|
70 |
+
except Exception as e:
|
71 |
+
raise HTTPException(status_code=500, detail=str(e))
|
72 |
|
73 |
|
74 |
@r.delete(
|
75 |
+
"/{email}",
|
76 |
+
response_model=UserResponse,
|
77 |
responses={
|
78 |
200: {"description": "User deleted"},
|
79 |
404: {"description": "User not found"},
|
80 |
500: {"description": "Internal server error"},
|
81 |
},
|
82 |
)
|
83 |
+
async def delete_user(email: str, db: AsyncSession = Depends(get_db_session)):
|
84 |
+
user = await UserModel.get(db, email=email)
|
85 |
if not user:
|
86 |
raise HTTPException(status_code=404, detail="User not found")
|
87 |
+
await UserModel.delete(db, email=email)
|
88 |
+
user = await UserModel.get(db, email=email)
|
89 |
return user
|
app/model/base.py
CHANGED
@@ -1,10 +1,15 @@
|
|
1 |
from datetime import datetime, timezone
|
2 |
-
from sqlalchemy import
|
|
|
3 |
|
4 |
from app.engine.postgresdb import Base
|
5 |
|
6 |
|
7 |
class BaseModel:
|
8 |
-
id =
|
9 |
-
created_at =
|
10 |
-
updated_at
|
|
|
|
|
|
|
|
|
|
1 |
from datetime import datetime, timezone
|
2 |
+
from sqlalchemy import DateTime
|
3 |
+
from sqlalchemy.orm import Mapped, mapped_column
|
4 |
|
5 |
from app.engine.postgresdb import Base
|
6 |
|
7 |
|
8 |
class BaseModel:
|
9 |
+
id: Mapped[int] = mapped_column(primary_key=True, index=True, autoincrement=True)
|
10 |
+
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=datetime.now(timezone.utc))
|
11 |
+
updated_at: Mapped[datetime] = mapped_column(
|
12 |
+
DateTime(timezone=True),
|
13 |
+
default=datetime.now(timezone.utc),
|
14 |
+
onupdate=datetime.now(timezone.utc),
|
15 |
+
)
|
app/model/user.py
CHANGED
@@ -1,3 +1,4 @@
|
|
|
|
1 |
from sqlalchemy.orm import relationship, Mapped, mapped_column
|
2 |
from sqlalchemy.sql import expression as sql
|
3 |
from sqlalchemy.ext.asyncio import AsyncSession
|
@@ -5,6 +6,8 @@ from sqlalchemy.ext.asyncio import AsyncSession
|
|
5 |
from app.model.base import BaseModel
|
6 |
from app.engine.postgresdb import Base
|
7 |
|
|
|
|
|
8 |
|
9 |
class User(Base, BaseModel):
|
10 |
__tablename__ = "users"
|
@@ -18,47 +21,40 @@ class User(Base, BaseModel):
|
|
18 |
|
19 |
@classmethod
|
20 |
async def create(cls: "type[User]", db: AsyncSession, **kwargs) -> "User":
|
21 |
-
|
22 |
-
|
|
|
|
|
|
|
23 |
await db.commit()
|
24 |
-
return
|
25 |
|
26 |
@classmethod
|
27 |
async def update(cls: "type[User]", db: AsyncSession, id: int, **kwargs) -> "User":
|
28 |
-
query = (
|
29 |
-
|
30 |
-
|
31 |
-
.values(**kwargs)
|
32 |
-
.execution_options(synchronize_session="fetch")
|
33 |
-
.returning(cls.id)
|
34 |
-
)
|
35 |
-
users = await db.execute(query)
|
36 |
await db.commit()
|
37 |
-
return users.first()
|
38 |
-
|
39 |
-
@classmethod
|
40 |
-
async def get(cls: "type[User]", db: AsyncSession, id: int) -> "User":
|
41 |
-
query = sql.select(cls).where(cls.id == id)
|
42 |
-
users = await db.execute(query)
|
43 |
-
(user,) = users.first()
|
44 |
return user
|
45 |
|
46 |
@classmethod
|
47 |
async def get(cls: "type[User]", db: AsyncSession, email: str) -> "User":
|
|
|
48 |
query = sql.select(cls).where(cls.email == email)
|
49 |
-
|
50 |
-
|
51 |
-
|
|
|
52 |
|
53 |
@classmethod
|
54 |
-
async def delete(cls: "type[User]", db: AsyncSession,
|
55 |
query = (
|
56 |
sql.update(cls)
|
57 |
-
.where(cls.
|
58 |
.values(is_deleted=True)
|
59 |
.execution_options(synchronize_session="fetch")
|
60 |
-
.returning(cls.id)
|
61 |
)
|
62 |
-
users = await db.
|
|
|
63 |
await db.commit()
|
64 |
-
return
|
|
|
1 |
+
import logging
|
2 |
from sqlalchemy.orm import relationship, Mapped, mapped_column
|
3 |
from sqlalchemy.sql import expression as sql
|
4 |
from sqlalchemy.ext.asyncio import AsyncSession
|
|
|
6 |
from app.model.base import BaseModel
|
7 |
from app.engine.postgresdb import Base
|
8 |
|
9 |
+
logger = logging.getLogger(__name__)
|
10 |
+
|
11 |
|
12 |
class User(Base, BaseModel):
|
13 |
__tablename__ = "users"
|
|
|
21 |
|
22 |
@classmethod
|
23 |
async def create(cls: "type[User]", db: AsyncSession, **kwargs) -> "User":
|
24 |
+
logging.info(f"Creating user: {kwargs}")
|
25 |
+
query = sql.insert(cls).values(**kwargs)
|
26 |
+
users = await db.scalars(query)
|
27 |
+
user = users.first()
|
28 |
+
logging.info(f"User created: {users.first()}")
|
29 |
await db.commit()
|
30 |
+
return user
|
31 |
|
32 |
@classmethod
|
33 |
async def update(cls: "type[User]", db: AsyncSession, id: int, **kwargs) -> "User":
|
34 |
+
query = sql.update(cls).where(cls.id == id).values(**kwargs).execution_options(synchronize_session="fetch")
|
35 |
+
users = await db.scalars(query)
|
36 |
+
user = users.first()
|
|
|
|
|
|
|
|
|
|
|
37 |
await db.commit()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
38 |
return user
|
39 |
|
40 |
@classmethod
|
41 |
async def get(cls: "type[User]", db: AsyncSession, email: str) -> "User":
|
42 |
+
logging.info(f"Getting user: {email}")
|
43 |
query = sql.select(cls).where(cls.email == email)
|
44 |
+
logging.info(f"Query: {query}")
|
45 |
+
users = await db.scalars(query)
|
46 |
+
logging.info(f"Users: {users}")
|
47 |
+
return users.first()
|
48 |
|
49 |
@classmethod
|
50 |
+
async def delete(cls: "type[User]", db: AsyncSession, email: str) -> "User":
|
51 |
query = (
|
52 |
sql.update(cls)
|
53 |
+
.where(cls.email == email)
|
54 |
.values(is_deleted=True)
|
55 |
.execution_options(synchronize_session="fetch")
|
|
|
56 |
)
|
57 |
+
users = await db.scalars(query)
|
58 |
+
user = users.first()
|
59 |
await db.commit()
|
60 |
+
return user
|
app/schema/base.py
CHANGED
@@ -2,7 +2,11 @@ from datetime import datetime
|
|
2 |
from pydantic import BaseModel
|
3 |
|
4 |
|
5 |
-
class
|
|
|
|
|
|
|
|
|
6 |
id: int
|
7 |
created_at: datetime
|
8 |
updated_at: datetime
|
|
|
2 |
from pydantic import BaseModel
|
3 |
|
4 |
|
5 |
+
class PydanticBaseModel(BaseModel):
|
6 |
+
pass
|
7 |
+
|
8 |
+
|
9 |
+
class BaseModel(PydanticBaseModel):
|
10 |
id: int
|
11 |
created_at: datetime
|
12 |
updated_at: datetime
|
app/schema/index.py
CHANGED
@@ -1,8 +1,8 @@
|
|
1 |
from enum import Enum
|
2 |
from datetime import datetime
|
3 |
-
from typing import List
|
4 |
|
5 |
-
from app.schema.base import BaseModel
|
6 |
|
7 |
|
8 |
class TransactionType(str, Enum):
|
@@ -10,12 +10,25 @@ class TransactionType(str, Enum):
|
|
10 |
EXPENSE = "expense"
|
11 |
|
12 |
|
13 |
-
class UserCreate(
|
14 |
name: str
|
15 |
email: str
|
16 |
hashed_password: str
|
17 |
|
18 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
19 |
class User(BaseModel):
|
20 |
name: str
|
21 |
email: str
|
|
|
1 |
from enum import Enum
|
2 |
from datetime import datetime
|
3 |
+
from typing import List, Optional
|
4 |
|
5 |
+
from app.schema.base import BaseModel, PydanticBaseModel
|
6 |
|
7 |
|
8 |
class TransactionType(str, Enum):
|
|
|
10 |
EXPENSE = "expense"
|
11 |
|
12 |
|
13 |
+
class UserCreate(PydanticBaseModel):
|
14 |
name: str
|
15 |
email: str
|
16 |
hashed_password: str
|
17 |
|
18 |
|
19 |
+
class UserUpdate(PydanticBaseModel):
|
20 |
+
name: str
|
21 |
+
email: str
|
22 |
+
hashed_password: str
|
23 |
+
|
24 |
+
|
25 |
+
class UserResponse(PydanticBaseModel):
|
26 |
+
id: int
|
27 |
+
name: str
|
28 |
+
email: str
|
29 |
+
is_deleted: bool
|
30 |
+
|
31 |
+
|
32 |
class User(BaseModel):
|
33 |
name: str
|
34 |
email: str
|
migration/versions/7ea44cbc5b1f_default_timezone.py
ADDED
@@ -0,0 +1,69 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"""default_timezone
|
2 |
+
|
3 |
+
Revision ID: 7ea44cbc5b1f
|
4 |
+
Revises: 8feaedca36f9
|
5 |
+
Create Date: 2024-06-02 14:36:01.552518
|
6 |
+
|
7 |
+
"""
|
8 |
+
|
9 |
+
from typing import Sequence, Union
|
10 |
+
|
11 |
+
from alembic import op
|
12 |
+
import sqlalchemy as sa
|
13 |
+
from sqlalchemy.dialects import postgresql
|
14 |
+
|
15 |
+
# revision identifiers, used by Alembic.
|
16 |
+
revision: str = "7ea44cbc5b1f"
|
17 |
+
down_revision: Union[str, None] = "8feaedca36f9"
|
18 |
+
branch_labels: Union[str, Sequence[str], None] = None
|
19 |
+
depends_on: Union[str, Sequence[str], None] = None
|
20 |
+
|
21 |
+
|
22 |
+
def upgrade() -> None:
|
23 |
+
# ### commands auto generated by Alembic - please adjust! ###
|
24 |
+
op.alter_column(
|
25 |
+
"transactions",
|
26 |
+
"created_at",
|
27 |
+
existing_type=postgresql.TIMESTAMP(),
|
28 |
+
type_=sa.DateTime(timezone=True),
|
29 |
+
nullable=False,
|
30 |
+
)
|
31 |
+
op.alter_column(
|
32 |
+
"transactions",
|
33 |
+
"updated_at",
|
34 |
+
existing_type=postgresql.TIMESTAMP(),
|
35 |
+
type_=sa.DateTime(timezone=True),
|
36 |
+
nullable=False,
|
37 |
+
)
|
38 |
+
op.alter_column(
|
39 |
+
"users", "created_at", existing_type=postgresql.TIMESTAMP(), type_=sa.DateTime(timezone=True), nullable=False
|
40 |
+
)
|
41 |
+
op.alter_column(
|
42 |
+
"users", "updated_at", existing_type=postgresql.TIMESTAMP(), type_=sa.DateTime(timezone=True), nullable=False
|
43 |
+
)
|
44 |
+
# ### end Alembic commands ###
|
45 |
+
|
46 |
+
|
47 |
+
def downgrade() -> None:
|
48 |
+
# ### commands auto generated by Alembic - please adjust! ###
|
49 |
+
op.alter_column(
|
50 |
+
"users", "updated_at", existing_type=sa.DateTime(timezone=True), type_=postgresql.TIMESTAMP(), nullable=True
|
51 |
+
)
|
52 |
+
op.alter_column(
|
53 |
+
"users", "created_at", existing_type=sa.DateTime(timezone=True), type_=postgresql.TIMESTAMP(), nullable=True
|
54 |
+
)
|
55 |
+
op.alter_column(
|
56 |
+
"transactions",
|
57 |
+
"updated_at",
|
58 |
+
existing_type=sa.DateTime(timezone=True),
|
59 |
+
type_=postgresql.TIMESTAMP(),
|
60 |
+
nullable=True,
|
61 |
+
)
|
62 |
+
op.alter_column(
|
63 |
+
"transactions",
|
64 |
+
"created_at",
|
65 |
+
existing_type=sa.DateTime(timezone=True),
|
66 |
+
type_=postgresql.TIMESTAMP(),
|
67 |
+
nullable=True,
|
68 |
+
)
|
69 |
+
# ### end Alembic commands ###
|