Skip to content

Commit f0fbf54

Browse files
authored
chore: minor improvements (#29)
* refactor: made seeding counts changeable inside seed_all * refactor: tags page * docs: improved routes documentation * test: extended test coverage * test: extended test coverage
1 parent ba19fb0 commit f0fbf54

21 files changed

Lines changed: 1377 additions & 73 deletions

File tree

backend/app/api/v1/routes/events.py

Lines changed: 37 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -126,12 +126,27 @@ def delete_event_route(event_id: int, db: Session = Depends(get_db)):
126126
return db_event
127127

128128

129-
@router.get("/{event_id}/schema.json", response_class=JSONResponse)
129+
@router.get(
130+
"/{event_id}/schema.json",
131+
response_class=JSONResponse,
132+
summary="Export event as JSON Schema",
133+
description="Generate a JSON Schema for the event's data structure. Useful for validation and documentation.",
134+
responses={
135+
200: {"description": "JSON Schema generated successfully"},
136+
404: {"description": "Event not found"},
137+
},
138+
)
130139
def get_event_json_schema(
131140
event_id: int,
132-
include_descriptions: bool = Query(True),
133-
include_examples: bool = Query(True),
134-
additional_properties: bool = Query(True),
141+
include_descriptions: bool = Query(
142+
True, description="Include field descriptions in schema"
143+
),
144+
include_examples: bool = Query(
145+
True, description="Include field examples in schema"
146+
),
147+
additional_properties: bool = Query(
148+
True, description="Allow additional properties in schema"
149+
),
135150
db: Session = Depends(get_db),
136151
):
137152
db_event = event_crud.get_event(db=db, event_id=event_id)
@@ -148,12 +163,26 @@ def get_event_json_schema(
148163
return schema
149164

150165

151-
@router.get("/{event_id}/schema.yaml")
166+
@router.get(
167+
"/{event_id}/schema.yaml",
168+
summary="Export event as YAML Schema",
169+
description="Generate a YAML Schema for the event's data structure. Same as JSON but in YAML format.",
170+
responses={
171+
200: {"description": "YAML Schema generated successfully"},
172+
404: {"description": "Event not found"},
173+
},
174+
)
152175
def get_event_yaml_schema(
153176
event_id: int,
154-
include_descriptions: bool = Query(True),
155-
include_examples: bool = Query(True),
156-
additional_properties: bool = Query(True),
177+
include_descriptions: bool = Query(
178+
True, description="Include field descriptions in schema"
179+
),
180+
include_examples: bool = Query(
181+
True, description="Include field examples in schema"
182+
),
183+
additional_properties: bool = Query(
184+
True, description="Allow additional properties in schema"
185+
),
157186
db: Session = Depends(get_db),
158187
):
159188
db_event = event_crud.get_event(db=db, event_id=event_id)

backend/app/api/v1/routes/fields.py

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@
1717
status_code=status.HTTP_201_CREATED,
1818
summary="Create a field",
1919
description="Create a new field that can be associated with events.",
20+
responses={
21+
201: {"description": "Field created successfully"},
22+
400: {"description": "Validation error"},
23+
},
2024
)
2125
def create_field_route(field: FieldCreate, db: Session = Depends(get_db)):
2226
return field_crud.create_field(db=db, field=field)
@@ -27,6 +31,7 @@ def create_field_route(field: FieldCreate, db: Session = Depends(get_db)):
2731
response_model=list[FieldOut],
2832
summary="List all fields",
2933
description="Return a paginated list of all fields that can be assigned to events.",
34+
responses={200: {"description": "List of fields returned successfully"}},
3035
)
3136
def list_fields_route(db: Session = Depends(get_db)):
3237
return field_crud.get_fields(db=db)
@@ -36,12 +41,17 @@ def list_fields_route(db: Session = Depends(get_db)):
3641
"/{field_id}",
3742
response_model=FieldOut | FieldOutWithEventCount,
3843
summary="Get field by ID",
39-
description="Return a single field by its ID.",
40-
responses={404: {"description": "Field not found"}},
44+
description="Return a single field by its ID. Optionally include count of events using this field.",
45+
responses={
46+
200: {"description": "Field found and returned"},
47+
404: {"description": "Field not found"},
48+
},
4149
)
4250
def get_field_route(
4351
field_id: int,
44-
with_event_count: bool = Query(False),
52+
with_event_count: bool = Query(
53+
False, description="Include count of events using this field"
54+
),
4555
db: Session = Depends(get_db),
4656
):
4757
db_field = field_crud.get_field(db=db, field_id=field_id)
@@ -60,7 +70,11 @@ def get_field_route(
6070
response_model=FieldOut,
6171
summary="Update a field",
6272
description="Update the name, description, or type of a field.",
63-
responses={404: {"description": "Field not found"}},
73+
responses={
74+
200: {"description": "Field updated successfully"},
75+
404: {"description": "Field not found"},
76+
400: {"description": "Validation error"},
77+
},
6478
)
6579
def update_field_route(
6680
field_id: int, field: FieldCreate, db: Session = Depends(get_db)
@@ -76,7 +90,10 @@ def update_field_route(
7690
response_model=FieldOut,
7791
summary="Delete a field",
7892
description="Delete a field by its ID. This will remove the field from all related events.",
79-
responses={404: {"description": "Field not found"}},
93+
responses={
94+
200: {"description": "Field deleted successfully"},
95+
404: {"description": "Field not found"},
96+
},
8097
)
8198
def delete_field_route(field_id: int, db: Session = Depends(get_db)):
8299
db_field = field_crud.delete_field(db=db, field_id=field_id)

backend/app/api/v1/routes/generic.py

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,26 +8,48 @@
88
router = APIRouter()
99

1010

11-
@router.get("/ping")
11+
@router.get(
12+
"/ping",
13+
summary="Health check",
14+
description="Simple health check endpoint to verify API is running.",
15+
responses={200: {"description": "API is healthy"}},
16+
)
1217
def ping(settings: Settings = Depends(get_settings)):
13-
return {"pong": True, "debug_mode": settings.debug}
18+
return {"pong": True, "debug_mode": settings.is_dev}
1419

1520

16-
@router.get("/config")
21+
@router.get(
22+
"/config",
23+
summary="Get configuration",
24+
description="Get basic configuration information about the API instance.",
25+
responses={200: {"description": "Configuration returned"}},
26+
)
1727
def get_config(settings: Settings = Depends(get_settings)):
1828
return {
1929
"database_url": settings.database_url,
20-
"debug": settings.debug,
30+
"debug": settings.is_dev,
2131
}
2232

2333

24-
@router.get("/link-types", response_model=list[str])
34+
@router.get(
35+
"/link-types",
36+
response_model=list[str],
37+
summary="Get link types",
38+
description="Get available link types for event external links (figma, confluence, etc).",
39+
responses={200: {"description": "List of available link types"}},
40+
)
2541
async def get_link_types(response: Response):
2642
response.headers["Cache-Control"] = "public, max-age=3600"
2743
return [link_type.value for link_type in LinkType]
2844

2945

30-
@router.get("/field-types", response_model=list[str])
46+
@router.get(
47+
"/field-types",
48+
response_model=list[str],
49+
summary="Get field types",
50+
description="Get available field data types (string, number, boolean, etc).",
51+
responses={200: {"description": "List of available field types"}},
52+
)
3153
async def get_field_types(response: Response):
3254
response.headers["Cache-Control"] = "public, max-age=3600"
3355
return [field_type.value for field_type in FieldType]

backend/app/api/v1/routes/tags.py

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@
1717
status_code=status.HTTP_201_CREATED,
1818
summary="Create a tag",
1919
description="Create a tag manually. Typically, tags are created automatically when creating or updating an event.",
20+
responses={
21+
201: {"description": "Tag created successfully"},
22+
400: {"description": "Validation error"},
23+
},
2024
)
2125
def create_tag_route(tag: TagCreate, db: Session = Depends(get_db)):
2226
return tag_crud.create_tag(db=db, tag=tag)
@@ -27,6 +31,7 @@ def create_tag_route(tag: TagCreate, db: Session = Depends(get_db)):
2731
response_model=list[TagOut],
2832
summary="List all tags",
2933
description="Return a paginated list of all tags available in the system.",
34+
responses={200: {"description": "List of tags returned successfully"}},
3035
)
3136
def list_tags_route(db: Session = Depends(get_db)):
3237
return tag_crud.get_tags(db=db)
@@ -37,7 +42,10 @@ def list_tags_route(db: Session = Depends(get_db)):
3742
response_model=TagOut,
3843
summary="Get tag by ID",
3944
description="Return a single tag by its unique identifier.",
40-
responses={404: {"description": "Tag not found"}},
45+
responses={
46+
200: {"description": "Tag found and returned"},
47+
404: {"description": "Tag not found"},
48+
},
4149
)
4250
def get_tag_route(tag_id: str, db: Session = Depends(get_db)):
4351
db_tag = tag_crud.get_tag(db=db, tag_id=tag_id)
@@ -51,7 +59,11 @@ def get_tag_route(tag_id: str, db: Session = Depends(get_db)):
5159
response_model=TagOut,
5260
summary="Update a tag",
5361
description="Update the description of an existing tag.",
54-
responses={404: {"description": "Tag not found"}},
62+
responses={
63+
200: {"description": "Tag updated successfully"},
64+
404: {"description": "Tag not found"},
65+
400: {"description": "Validation error"},
66+
},
5567
)
5668
def update_tag_route(tag_id: str, tag: TagCreate, db: Session = Depends(get_db)):
5769
db_tag = tag_crud.update_tag(db=db, tag_id=tag_id, tag=tag)
@@ -65,7 +77,10 @@ def update_tag_route(tag_id: str, tag: TagCreate, db: Session = Depends(get_db))
6577
response_model=TagOut,
6678
summary="Delete a tag",
6779
description="Delete a tag by its ID. This will remove the tag from all related events.",
68-
responses={404: {"description": "Tag not found"}},
80+
responses={
81+
200: {"description": "Tag deleted successfully"},
82+
404: {"description": "Tag not found"},
83+
},
6984
)
7085
def delete_tag_route(tag_id: str, db: Session = Depends(get_db)):
7186
db_tag = tag_crud.delete_tag(db=db, tag_id=tag_id)

backend/app/modules/admin/seed/service.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@
66
from app.shared.service import assert_db_empty
77

88

9-
def seed_all(db: Session):
9+
def seed_all(db: Session, n_tags=10, n_fields=10, n_events=10):
1010
assert_db_empty(db)
11-
seed_tags(db)
12-
seed_fields(db)
13-
seed_events(db)
11+
seed_tags(db, count=n_tags)
12+
seed_fields(db, count=n_fields)
13+
seed_events(db, count=n_events)

backend/app/modules/auth/router.py

Lines changed: 78 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -29,14 +29,29 @@
2929
response_model=TokenOut,
3030
status_code=status.HTTP_201_CREATED,
3131
dependencies=[Depends(ensure_not_demo)],
32+
summary="Create user account",
33+
description="Register a new user account with email and password. Returns authentication token.",
34+
responses={
35+
201: {"description": "User created successfully"},
36+
400: {"description": "User already exists or validation error"},
37+
},
3238
)
3339
def signup(user_in: UserCreate, db: Session = Depends(get_db)):
3440
user = service.create_user(db, user_in)
3541
token = create_access_token({"sub": user.email})
3642
return {"access_token": token, "token_type": "bearer"}
3743

3844

39-
@router.post("/login", response_model=TokenOut)
45+
@router.post(
46+
"/login",
47+
response_model=TokenOut,
48+
summary="Login with email and password",
49+
description="Authenticate user with email and password credentials. Returns authentication token.",
50+
responses={
51+
200: {"description": "Login successful"},
52+
401: {"description": "Invalid credentials"},
53+
},
54+
)
4055
def login(user_in: UserLogin, db: Session = Depends(get_db)):
4156
user = crud.get_user_by_email(db, user_in.email)
4257
if (
@@ -51,20 +66,47 @@ def login(user_in: UserLogin, db: Session = Depends(get_db)):
5166
return {"access_token": token, "token_type": "bearer"}
5267

5368

54-
@router.post("/oauth", response_model=TokenOut, dependencies=[Depends(ensure_not_demo)])
69+
@router.post(
70+
"/oauth",
71+
response_model=TokenOut,
72+
dependencies=[Depends(ensure_not_demo)],
73+
summary="OAuth login",
74+
description="Authenticate user with OAuth provider (GitHub, Google). Creates account if it doesn't exist.",
75+
responses={
76+
200: {"description": "OAuth login successful"},
77+
400: {"description": "Invalid OAuth payload"},
78+
},
79+
)
5580
def login_oauth(payload: OAuthLogin, db: Session = Depends(get_db)):
5681
email = oauth.get_email_from_oauth(payload)
5782
user = service.get_or_create_oauth_user(db, email=email, provider=payload.provider)
5883
token = create_access_token({"sub": user.email})
5984
return {"access_token": token, "token_type": "bearer"}
6085

6186

62-
@router.get("/me", response_model=schemas.UserOut)
87+
@router.get(
88+
"/me",
89+
response_model=schemas.UserOut,
90+
summary="Get current user",
91+
description="Get details of the currently authenticated user.",
92+
responses={
93+
200: {"description": "User details returned"},
94+
401: {"description": "Not authenticated"},
95+
},
96+
)
6397
def read_current_user(current_user: User = Depends(get_current_user)):
6498
return current_user
6599

66100

67-
@router.post("/token")
101+
@router.post(
102+
"/token",
103+
summary="Get access token (OAuth2 compatible)",
104+
description="OAuth2 compatible token endpoint for form-based authentication.",
105+
responses={
106+
200: {"description": "Token generated successfully"},
107+
400: {"description": "Invalid credentials"},
108+
},
109+
)
68110
def login_for_access_token(
69111
form_data: OAuth2PasswordRequestForm = Depends(),
70112
db: Session = Depends(get_db),
@@ -79,11 +121,22 @@ def login_for_access_token(
79121
return {"access_token": token, "token_type": "bearer"}
80122

81123

82-
@router.get("/oauth/init/{provider}", dependencies=[Depends(ensure_not_demo)])
124+
@router.get(
125+
"/oauth/init/{provider}",
126+
dependencies=[Depends(ensure_not_demo)],
127+
summary="Start OAuth flow",
128+
description="Initiate OAuth authentication with GitHub or Google. Redirects to provider.",
129+
responses={
130+
302: {"description": "Redirect to OAuth provider"},
131+
400: {"description": "Invalid provider"},
132+
},
133+
)
83134
def start_oauth_login(
84135
provider: ProviderName,
85136
request: Request,
86-
redirect: str = Query("/events"),
137+
redirect: str = Query(
138+
"/events", description="Where to redirect after successful login"
139+
),
87140
):
88141
redirect_uri = request.url_for("oauth_callback")
89142

@@ -98,10 +151,20 @@ def start_oauth_login(
98151

99152

100153
@router.get(
101-
"/oauth/callback", name="oauth_callback", dependencies=[Depends(ensure_not_demo)]
154+
"/oauth/callback",
155+
name="oauth_callback",
156+
dependencies=[Depends(ensure_not_demo)],
157+
summary="OAuth callback",
158+
description="Handle OAuth provider callback. Internal endpoint used by OAuth flow.",
159+
responses={
160+
302: {"description": "Redirect to frontend with auth code"},
161+
400: {"description": "Invalid callback parameters"},
162+
},
102163
)
103164
def handle_oauth_callback(
104-
code: str = Query(...), state: str = Query(...), settings=Depends(get_settings)
165+
code: str = Query(..., description="OAuth authorization code from provider"),
166+
state: str = Query(..., description="OAuth state parameter"),
167+
settings=Depends(get_settings),
105168
):
106169
try:
107170
decoded = json.loads(base64.b64decode(state).decode())
@@ -116,6 +179,12 @@ def handle_oauth_callback(
116179
return RedirectResponse(url=final_url)
117180

118181

119-
@router.get("/providers", tags=["auth"])
182+
@router.get(
183+
"/providers",
184+
tags=["auth"],
185+
summary="List OAuth providers",
186+
description="Get list of available OAuth authentication providers.",
187+
responses={200: {"description": "List of available OAuth providers"}},
188+
)
120189
def list_oauth_providers(settings=Depends(get_settings)):
121190
return {"providers": settings.available_oauth_providers}

0 commit comments

Comments
 (0)