Skip to content

Commit ddb7887

Browse files
authored
Merge branch 'tiangolo:master' into master
2 parents d6441b1 + 763afde commit ddb7887

25 files changed

Lines changed: 1733 additions & 1553 deletions
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/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/poetry.lock

Lines changed: 10 additions & 10 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

backend/pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ jinja2 = "^3.1.4"
2020
alembic = "^1.12.1"
2121
httpx = "^0.25.1"
2222
psycopg = {extras = ["binary"], version = "^3.1.13"}
23-
sqlmodel = "^0.0.16"
23+
sqlmodel = "^0.0.19"
2424
# Pin bcrypt until passlib supports the latest
2525
bcrypt = "4.0.1"
2626
pydantic-settings = "^2.2.1"

docker-compose.override.yml

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
services:
22

33
proxy:
4-
image: traefik:v2.3
4+
image: traefik:3.0
55
volumes:
66
- /var/run/docker.sock:/var/run/docker.sock
77
ports:
@@ -62,6 +62,17 @@ services:
6262
INSTALL_DEV: ${INSTALL_DEV-true}
6363
# command: sleep infinity # Infinite loop to keep container alive doing nothing
6464
command: /start-reload.sh
65+
environment:
66+
SMTP_HOST: "mailcatcher"
67+
SMTP_PORT: "1025"
68+
SMTP_TLS: "false"
69+
EMAILS_FROM_EMAIL: "noreply@example.com"
70+
71+
mailcatcher:
72+
image: schickling/mailcatcher
73+
ports:
74+
- "1080:1080"
75+
- "1025:1025"
6576

6677
frontend:
6778
restart: "no"

docker-compose.traefik.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
services:
22
traefik:
3-
image: traefik:v2.3
3+
image: traefik:3.0
44
ports:
55
# Listen on port 80, default for HTTP, necessary to redirect to HTTPS
66
- 80:80

docker-compose.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -76,10 +76,10 @@ services:
7676

7777
- traefik.http.services.${STACK_NAME?Variable not set}-backend.loadbalancer.server.port=80
7878

79-
- traefik.http.routers.${STACK_NAME?Variable not set}-backend-http.rule=Host(`${DOMAIN?Variable not set}`, `www.${DOMAIN?Variable not set}`) && PathPrefix(`/api`, `/docs`, `/redoc`)
79+
- traefik.http.routers.${STACK_NAME?Variable not set}-backend-http.rule=(Host(`${DOMAIN?Variable not set}`) || Host(`www.${DOMAIN?Variable not set}`)) && (PathPrefix(`/api`) || PathPrefix(`/docs`) || PathPrefix(`/redoc`))
8080
- traefik.http.routers.${STACK_NAME?Variable not set}-backend-http.entrypoints=http
8181

82-
- traefik.http.routers.${STACK_NAME?Variable not set}-backend-https.rule=Host(`${DOMAIN?Variable not set}`, `www.${DOMAIN?Variable not set}`) && PathPrefix(`/api`, `/docs`, `/redoc`)
82+
- traefik.http.routers.${STACK_NAME?Variable not set}-backend-https.rule=(Host(`${DOMAIN?Variable not set}`) || Host(`www.${DOMAIN?Variable not set}`)) && (PathPrefix(`/api`) || PathPrefix(`/docs`) || PathPrefix(`/redoc`))
8383
- traefik.http.routers.${STACK_NAME?Variable not set}-backend-https.entrypoints=https
8484
- traefik.http.routers.${STACK_NAME?Variable not set}-backend-https.tls=true
8585
- traefik.http.routers.${STACK_NAME?Variable not set}-backend-https.tls.certresolver=le
@@ -111,10 +111,10 @@ services:
111111

112112
- traefik.http.services.${STACK_NAME?Variable not set}-frontend.loadbalancer.server.port=80
113113

114-
- traefik.http.routers.${STACK_NAME?Variable not set}-frontend-http.rule=Host(`${DOMAIN?Variable not set}`, `www.${DOMAIN?Variable not set}`)
114+
- traefik.http.routers.${STACK_NAME?Variable not set}-frontend-http.rule=Host(`${DOMAIN?Variable not set}`) || Host(`www.${DOMAIN?Variable not set}`)
115115
- traefik.http.routers.${STACK_NAME?Variable not set}-frontend-http.entrypoints=http
116116

117-
- traefik.http.routers.${STACK_NAME?Variable not set}-frontend-https.rule=Host(`${DOMAIN?Variable not set}`, `www.${DOMAIN?Variable not set}`)
117+
- traefik.http.routers.${STACK_NAME?Variable not set}-frontend-https.rule=Host(`${DOMAIN?Variable not set}`) || Host(`www.${DOMAIN?Variable not set}`)
118118
- traefik.http.routers.${STACK_NAME?Variable not set}-frontend-https.entrypoints=https
119119
- traefik.http.routers.${STACK_NAME?Variable not set}-frontend-https.tls=true
120120
- traefik.http.routers.${STACK_NAME?Variable not set}-frontend-https.tls.certresolver=le

frontend/biome.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
"enabled": true
55
},
66
"files": {
7-
"ignore": ["node_modules", "src/client/", "src/routeTree.gen.ts"]
7+
"ignore": ["node_modules", "src/routeTree.gen.ts"]
88
},
99
"linter": {
1010
"enabled": true,

frontend/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
"build": "tsc && vite build",
99
"lint": "biome check --apply-unsafe --no-errors-on-unmatched --files-ignore-unknown=true ./",
1010
"preview": "vite preview",
11-
"generate-client": "openapi-ts --input ./openapi.json --output ./src/client --client axios --exportSchemas true"
11+
"generate-client": "openapi-ts --input ./openapi.json --output ./src/client --client axios --exportSchemas true && biome format --write ./src/client"
1212
},
1313
"dependencies": {
1414
"@chakra-ui/icons": "2.1.1",
Lines changed: 21 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,25 @@
1-
import type { ApiRequestOptions } from './ApiRequestOptions';
2-
import type { ApiResult } from './ApiResult';
1+
import type { ApiRequestOptions } from "./ApiRequestOptions"
2+
import type { ApiResult } from "./ApiResult"
33

44
export class ApiError extends Error {
5-
public readonly url: string;
6-
public readonly status: number;
7-
public readonly statusText: string;
8-
public readonly body: unknown;
9-
public readonly request: ApiRequestOptions;
5+
public readonly url: string
6+
public readonly status: number
7+
public readonly statusText: string
8+
public readonly body: unknown
9+
public readonly request: ApiRequestOptions
1010

11-
constructor(request: ApiRequestOptions, response: ApiResult, message: string) {
12-
super(message);
11+
constructor(
12+
request: ApiRequestOptions,
13+
response: ApiResult,
14+
message: string,
15+
) {
16+
super(message)
1317

14-
this.name = 'ApiError';
15-
this.url = response.url;
16-
this.status = response.status;
17-
this.statusText = response.statusText;
18-
this.body = response.body;
19-
this.request = request;
20-
}
21-
}
18+
this.name = "ApiError"
19+
this.url = response.url
20+
this.status = response.status
21+
this.statusText = response.statusText
22+
this.body = response.body
23+
this.request = request
24+
}
25+
}

0 commit comments

Comments
 (0)