Skip to content

Commit e8e3026

Browse files
authored
Merge branch 'master' into feature/allow-running-specific-test
2 parents 03c20ee + 91c3533 commit e8e3026

40 files changed

+1816
-1810
lines changed

.github/workflows/smokeshow.yml

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,14 @@ jobs:
2020

2121
- run: pip install smokeshow
2222

23-
- uses: dawidd6/action-download-artifact@v3.1.4
23+
- uses: actions/download-artifact@v4
2424
with:
25-
workflow: test.yml
26-
commit: ${{ github.event.workflow_run.head_sha }}
27-
github_token: ${{ secrets.GITHUB_TOKEN }}
25+
name: coverage-html
26+
path: backend/htmlcov
27+
github-token: ${{ secrets.GITHUB_TOKEN }}
28+
run-id: ${{ github.event.workflow_run.id }}
2829

29-
- run: smokeshow upload coverage-html
30+
- run: smokeshow upload backend/htmlcov
3031
env:
3132
SMOKESHOW_GITHUB_STATUS_DESCRIPTION: Coverage {coverage-percentage}
3233
SMOKESHOW_GITHUB_COVERAGE_THRESHOLD: 90

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
- 🦇 Dark mode support.
1717
- 🐋 [Docker Compose](https://www.docker.com) for development and production.
1818
- 🔒 Secure password hashing by default.
19-
- 🔑 JWT token authentication.
19+
- 🔑 JWT (JSON Web Token) authentication.
2020
- 📫 Email based password recovery.
2121
- ✅ Tests with [Pytest](https://pytest.org).
2222
- 📞 [Traefik](https://traefik.io) as a reverse proxy / load balancer.
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
"""Add max length for string(varchar) fields in User and Items models
2+
3+
Revision ID: 9c0a54914c78
4+
Revises: e2412789c190
5+
Create Date: 2024-06-17 14:42:44.639457
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 = '9c0a54914c78'
15+
down_revision = 'e2412789c190'
16+
branch_labels = None
17+
depends_on = None
18+
19+
20+
def upgrade():
21+
# Adjust the length of the email field in the User table
22+
op.alter_column('user', 'email',
23+
existing_type=sa.String(),
24+
type_=sa.String(length=255),
25+
existing_nullable=False)
26+
27+
# Adjust the length of the full_name field in the User table
28+
op.alter_column('user', 'full_name',
29+
existing_type=sa.String(),
30+
type_=sa.String(length=255),
31+
existing_nullable=True)
32+
33+
# Adjust the length of the title field in the Item table
34+
op.alter_column('item', 'title',
35+
existing_type=sa.String(),
36+
type_=sa.String(length=255),
37+
existing_nullable=False)
38+
39+
# Adjust the length of the description field in the Item table
40+
op.alter_column('item', 'description',
41+
existing_type=sa.String(),
42+
type_=sa.String(length=255),
43+
existing_nullable=True)
44+
45+
46+
def downgrade():
47+
# Revert the length of the email field in the User table
48+
op.alter_column('user', 'email',
49+
existing_type=sa.String(length=255),
50+
type_=sa.String(),
51+
existing_nullable=False)
52+
53+
# Revert the length of the full_name field in the User table
54+
op.alter_column('user', 'full_name',
55+
existing_type=sa.String(length=255),
56+
type_=sa.String(),
57+
existing_nullable=True)
58+
59+
# Revert the length of the title field in the Item table
60+
op.alter_column('item', 'title',
61+
existing_type=sa.String(length=255),
62+
type_=sa.String(),
63+
existing_nullable=False)
64+
65+
# Revert the length of the description field in the Item table
66+
op.alter_column('item', 'description',
67+
existing_type=sa.String(length=255),
68+
type_=sa.String(),
69+
existing_nullable=True)

backend/app/api/deps.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
from collections.abc import Generator
22
from typing import Annotated
33

4+
import jwt
45
from fastapi import Depends, HTTPException, status
56
from fastapi.security import OAuth2PasswordBearer
6-
from jose import JWTError, jwt
7+
from jwt.exceptions import InvalidTokenError
78
from pydantic import ValidationError
89
from sqlmodel import Session
910

@@ -32,7 +33,7 @@ def get_current_user(session: SessionDep, token: TokenDep) -> User:
3233
token, settings.SECRET_KEY, algorithms=[security.ALGORITHM]
3334
)
3435
token_data = TokenPayload(**payload)
35-
except (JWTError, ValidationError):
36+
except (InvalidTokenError, ValidationError):
3637
raise HTTPException(
3738
status_code=status.HTTP_403_FORBIDDEN,
3839
detail="Could not validate credentials",

backend/app/core/security.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from datetime import datetime, timedelta
22
from typing import Any
33

4-
from jose import jwt
4+
import jwt
55
from passlib.context import CryptContext
66

77
from app.core.config import settings

backend/app/models.py

Lines changed: 19 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,40 @@
1+
from pydantic import EmailStr
12
from sqlmodel import Field, Relationship, SQLModel
23

34

45
# Shared properties
5-
# TODO replace email str with EmailStr when sqlmodel supports it
66
class UserBase(SQLModel):
7-
email: str = Field(unique=True, index=True)
7+
email: EmailStr = Field(unique=True, index=True, max_length=255)
88
is_active: bool = True
99
is_superuser: bool = False
10-
full_name: str | None = None
10+
full_name: str | None = Field(default=None, max_length=255)
1111

1212

1313
# Properties to receive via API on creation
1414
class UserCreate(UserBase):
15-
password: str
15+
password: str = Field(min_length=8, max_length=40)
1616

1717

18-
# TODO replace email str with EmailStr when sqlmodel supports it
1918
class UserRegister(SQLModel):
20-
email: str
21-
password: str
22-
full_name: str | None = None
19+
email: EmailStr = Field(max_length=255)
20+
password: str = Field(min_length=8, max_length=40)
21+
full_name: str | None = Field(default=None, max_length=255)
2322

2423

2524
# Properties to receive via API on update, all are optional
26-
# TODO replace email str with EmailStr when sqlmodel supports it
2725
class UserUpdate(UserBase):
28-
email: str | None = None # type: ignore
29-
password: str | None = None
26+
email: EmailStr | None = Field(default=None, max_length=255) # type: ignore
27+
password: str | None = Field(default=None, min_length=8, max_length=40)
3028

3129

32-
# TODO replace email str with EmailStr when sqlmodel supports it
3330
class UserUpdateMe(SQLModel):
34-
full_name: str | None = None
35-
email: str | None = None
31+
full_name: str | None = Field(default=None, max_length=255)
32+
email: EmailStr | None = Field(default=None, max_length=255)
3633

3734

3835
class UpdatePassword(SQLModel):
39-
current_password: str
40-
new_password: str
36+
current_password: str = Field(min_length=8, max_length=40)
37+
new_password: str = Field(min_length=8, max_length=40)
4138

4239

4340
# Database model, database table inferred from class name
@@ -59,24 +56,24 @@ class UsersPublic(SQLModel):
5956

6057
# Shared properties
6158
class ItemBase(SQLModel):
62-
title: str
63-
description: str | None = None
59+
title: str = Field(min_length=1, max_length=255)
60+
description: str | None = Field(default=None, max_length=255)
6461

6562

6663
# Properties to receive on item creation
6764
class ItemCreate(ItemBase):
68-
title: str
65+
title: str = Field(min_length=1, max_length=255)
6966

7067

7168
# Properties to receive on item update
7269
class ItemUpdate(ItemBase):
73-
title: str | None = None # type: ignore
70+
title: str | None = Field(default=None, min_length=1, max_length=255) # type: ignore
7471

7572

7673
# Database model, database table inferred from class name
7774
class Item(ItemBase, table=True):
7875
id: int | None = Field(default=None, primary_key=True)
79-
title: str
76+
title: str = Field(max_length=255)
8077
owner_id: int | None = Field(default=None, foreign_key="user.id", nullable=False)
8178
owner: User | None = Relationship(back_populates="items")
8279

@@ -110,4 +107,4 @@ class TokenPayload(SQLModel):
110107

111108
class NewPassword(SQLModel):
112109
token: str
113-
new_password: str
110+
new_password: str = Field(min_length=8, max_length=40)

backend/app/utils.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,9 @@
55
from typing import Any
66

77
import emails # type: ignore
8+
import jwt
89
from jinja2 import Template
9-
from jose import JWTError, jwt
10+
from jwt.exceptions import InvalidTokenError
1011

1112
from app.core.config import settings
1213

@@ -112,5 +113,5 @@ def verify_password_reset_token(token: str) -> str | None:
112113
try:
113114
decoded_token = jwt.decode(token, settings.SECRET_KEY, algorithms=["HS256"])
114115
return str(decoded_token["sub"])
115-
except JWTError:
116+
except InvalidTokenError:
116117
return None

0 commit comments

Comments
 (0)