Skip to content

Commit 7b1a7fc

Browse files
committed
routes
1 parent 4454e46 commit 7b1a7fc

35 files changed

+314
-150
lines changed

backend/app/api/__init__.py

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,18 @@
11
from fastapi import APIRouter
22

3-
public_api = APIRouter()
4-
private_api = APIRouter()
3+
# When using HASSIO_MODE (Home Assistant ingress with automatic login), we make this distinction:
4+
# - api_public: exported to the local network via port 8989, no authentication required
5+
# - api_no_auth: accessible via Home Assistant ingress (on port 8990) without authentication
6+
# - api_with_auth: accessible via Home Assistant ingress (on port 8990) without authentication
7+
8+
# When not using HASSIO_MODE (running directly from Docker), we make this distinction:
9+
# - api_public: routes that do not require authentication
10+
# - api_no_auth: routes that do not require authentication
11+
# - api_with_auth: routes that can only be accessed by authenticated users
12+
13+
api_public = APIRouter()
14+
api_no_auth = APIRouter()
15+
api_with_auth = APIRouter()
516

617
from .auth import * # noqa: E402, F403
718
from .apps import * # noqa: E402, F403

backend/app/api/apps.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,24 +19,24 @@
1919
EnhanceSourceRequest,
2020
EnhanceSourceResponse
2121
)
22-
from . import private_api
22+
from . import api_with_auth
2323

2424
from typing import Optional
2525

26-
@private_api.get("/apps", response_model=AppsListResponse)
26+
@api_with_auth.get("/apps", response_model=AppsListResponse)
2727
async def api_apps_list(db: Session = Depends(get_db)):
2828
return {"apps": get_app_configs()}
2929

3030

31-
@private_api.get("/apps/source", response_model=AppsSourceResponse)
31+
@api_with_auth.get("/apps/source", response_model=AppsSourceResponse)
3232
async def api_apps_source(keyword: Optional[str] = None, db: Session = Depends(get_db)):
3333
sources = get_one_app_sources(keyword)
3434
if sources is None:
3535
raise HTTPException(status_code=404, detail="App sources not found")
3636
return sources
3737

3838

39-
@private_api.post("/apps/validate_source", response_model=ValidateSourceResponse)
39+
@api_with_auth.post("/apps/validate_source", response_model=ValidateSourceResponse)
4040
async def validate_python_frame_source(data: ValidateSourceRequest):
4141
file = data.file
4242
source = data.source
@@ -56,7 +56,7 @@ async def validate_python_frame_source(data: ValidateSourceRequest):
5656
return {"errors": errors}
5757

5858

59-
@private_api.post("/apps/enhance_source", response_model=EnhanceSourceResponse)
59+
@api_with_auth.post("/apps/enhance_source", response_model=EnhanceSourceResponse)
6060
async def enhance_python_frame_source(data: EnhanceSourceRequest, db: Session = Depends(get_db)):
6161
source = data.source
6262
prompt = data.prompt

backend/app/api/auth.py

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
from werkzeug.security import generate_password_hash, check_password_hash
1616
from app.schemas.auth import Token, UserSignup
1717

18-
from . import public_api
18+
from . import api_no_auth
1919

2020
config = get_config()
2121
SECRET_KEY = config.SECRET_KEY
@@ -35,6 +35,14 @@ def create_access_token(data: dict, expires_delta: Optional[datetime.timedelta]
3535
return encoded_jwt
3636

3737
async def get_current_user(token: str = Depends(oauth2_scheme), db: Session = Depends(get_db)):
38+
# if config.HASSIO_MODE == "ingress":
39+
# user = db.query(User).first()
40+
# if user is None:
41+
# user = User(email="[email protected]", password="")
42+
# db.add(user)
43+
# db.commit()
44+
# return user
45+
3846
credentials_exception = HTTPException(
3947
status_code=status.HTTP_401_UNAUTHORIZED,
4048
detail="Could not validate credentials",
@@ -53,8 +61,10 @@ async def get_current_user(token: str = Depends(oauth2_scheme), db: Session = De
5361
raise credentials_exception
5462
return user
5563

56-
@public_api.post("/login", response_model=Token)
64+
@api_no_auth.post("/login", response_model=Token)
5765
async def login(request: Request, form_data: OAuth2PasswordRequestForm = Depends(), db: Session = Depends(get_db), redis: Redis = Depends(get_redis)):
66+
if config.HASSIO_MODE is not None:
67+
raise HTTPException(status_code=401, detail="Login not allowed with HASSIO_MODE")
5868
email = form_data.username
5969
password = form_data.password
6070
ip = request.client.host
@@ -76,8 +86,11 @@ async def login(request: Request, form_data: OAuth2PasswordRequestForm = Depends
7686
access_token = create_access_token(data={"sub": user.email}, expires_delta=access_token_expires)
7787
return {"access_token": access_token, "token_type": "bearer"}
7888

79-
@public_api.post("/signup")
89+
@api_no_auth.post("/signup")
8090
async def signup(data: UserSignup, db: Session = Depends(get_db)):
91+
if config.HASSIO_MODE is not None:
92+
raise HTTPException(status_code=401, detail="Signup not allowed with HASSIO_MODE")
93+
8194
# Check if there is already a user registered (one-user system)
8295
if db.query(User).first() is not None:
8396
raise HTTPException(status_code=400, detail="Only one user is allowed. Please login!")

backend/app/api/frames.py

Lines changed: 23 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -25,28 +25,29 @@
2525
FrameMetricsResponse, FrameImageLinkResponse, FrameStateResponse,
2626
FrameAssetsResponse, FrameCreateRequest, FrameUpdateRequest
2727
)
28-
from app.api.auth import ALGORITHM, SECRET_KEY, get_current_user
28+
from app.api.auth import ALGORITHM, SECRET_KEY
2929
from app.utils.network import is_safe_host
3030
from app.redis import get_redis
31-
from . import private_api, public_api
31+
from app.config import Config, get_config
32+
from . import api_with_auth, api_no_auth
3233

3334

34-
@private_api.get("/frames", response_model=FramesListResponse)
35+
@api_with_auth.get("/frames", response_model=FramesListResponse)
3536
async def api_frames_list(db: Session = Depends(get_db)):
3637
frames = db.query(Frame).all()
3738
frames_list = [frame.to_dict() for frame in frames]
3839
return {"frames": frames_list}
3940

4041

41-
@private_api.get("/frames/{id:int}", response_model=FrameResponse)
42+
@api_with_auth.get("/frames/{id:int}", response_model=FrameResponse)
4243
async def api_frame_get(id: int, db: Session = Depends(get_db)):
4344
frame = db.get(Frame, id)
4445
if frame is None:
4546
raise HTTPException(status_code=HTTPStatus.NOT_FOUND, detail="Frame not found")
4647
return {"frame": frame.to_dict()}
4748

4849

49-
@private_api.get("/frames/{id:int}/logs", response_model=FrameLogsResponse)
50+
@api_with_auth.get("/frames/{id:int}/logs", response_model=FrameLogsResponse)
5051
async def api_frame_get_logs(id: int, db: Session = Depends(get_db)):
5152
frame = db.get(Frame, id)
5253
if frame is None:
@@ -55,8 +56,8 @@ async def api_frame_get_logs(id: int, db: Session = Depends(get_db)):
5556
return {"logs": logs}
5657

5758

58-
@private_api.get("/frames/{id:int}/image_link", response_model=FrameImageLinkResponse)
59-
async def get_image_link(id: int, user=Depends(get_current_user)):
59+
@api_with_auth.get("/frames/{id:int}/image_link", response_model=FrameImageLinkResponse)
60+
async def get_image_link(id: int, config: Config = Depends(get_config)):
6061
expire_minutes = 5
6162
now = datetime.utcnow()
6263
expire = now + timedelta(minutes=expire_minutes)
@@ -66,11 +67,11 @@ async def get_image_link(id: int, user=Depends(get_current_user)):
6667
expires_in = int((expire - now).total_seconds())
6768

6869
return {
69-
"url": f"/api/frames/{id}/image?token={token}",
70+
"url": config.base_path + f"/api/frames/{id}/image?token={token}",
7071
"expires_in": expires_in
7172
}
7273

73-
@public_api.get("/frames/{id:int}/image")
74+
@api_no_auth.get("/frames/{id:int}/image")
7475
async def api_frame_get_image(id: int, token: str, request: Request, db: Session = Depends(get_db), redis: Redis = Depends(get_redis)):
7576
try:
7677
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
@@ -109,7 +110,7 @@ async def api_frame_get_image(id: int, token: str, request: Request, db: Session
109110
raise HTTPException(status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail=str(e))
110111

111112

112-
@private_api.get("/frames/{id:int}/state", response_model=FrameStateResponse)
113+
@api_with_auth.get("/frames/{id:int}/state", response_model=FrameStateResponse)
113114
async def api_frame_get_state(id: int, db: Session = Depends(get_db), redis: Redis = Depends(get_redis)):
114115
frame = db.get(Frame, id)
115116
if frame is None:
@@ -145,7 +146,7 @@ async def api_frame_get_state(id: int, db: Session = Depends(get_db), redis: Red
145146
raise HTTPException(status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail=str(e))
146147

147148

148-
@private_api.post("/frames/{id:int}/event/{event}")
149+
@api_with_auth.post("/frames/{id:int}/event/{event}")
149150
async def api_frame_event(id: int, event: str, request: Request, db: Session = Depends(get_db)):
150151
frame = db.get(Frame, id)
151152
if frame is None:
@@ -177,7 +178,7 @@ async def api_frame_event(id: int, event: str, request: Request, db: Session = D
177178
raise HTTPException(status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail=str(e))
178179

179180

180-
@private_api.get("/frames/{id:int}/scene_source/{scene}")
181+
@api_with_auth.get("/frames/{id:int}/scene_source/{scene}")
181182
async def api_frame_scene_source(id: int, scene: str, db: Session = Depends(get_db)):
182183
frame = db.get(Frame, id)
183184
if frame is None:
@@ -189,7 +190,7 @@ async def api_frame_scene_source(id: int, scene: str, db: Session = Depends(get_
189190
raise HTTPException(status_code=HTTPStatus.NOT_FOUND, detail=f"Scene {scene} not found")
190191

191192

192-
@private_api.get("/frames/{id:int}/assets", response_model=FrameAssetsResponse)
193+
@api_with_auth.get("/frames/{id:int}/assets", response_model=FrameAssetsResponse)
193194
async def api_frame_get_assets(id: int, db: Session = Depends(get_db), redis: Redis = Depends(get_redis)):
194195
frame = db.get(Frame, id)
195196
if frame is None:
@@ -216,7 +217,7 @@ async def api_frame_get_assets(id: int, db: Session = Depends(get_db), redis: Re
216217
return {"assets": assets}
217218

218219

219-
@private_api.get("/frames/{id:int}/asset")
220+
@api_with_auth.get("/frames/{id:int}/asset")
220221
async def api_frame_get_asset(id: int, request: Request, db: Session = Depends(get_db), redis: Redis = Depends(get_redis)):
221222
frame = db.get(Frame, id)
222223
if frame is None:
@@ -279,7 +280,7 @@ async def api_frame_get_asset(id: int, request: Request, db: Session = Depends(g
279280
raise HTTPException(status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail=str(e))
280281

281282

282-
@private_api.post("/frames/{id:int}/reset")
283+
@api_with_auth.post("/frames/{id:int}/reset")
283284
async def api_frame_reset_event(id: int, redis: Redis = Depends(get_redis)):
284285
try:
285286
from app.tasks import reset_frame
@@ -289,7 +290,7 @@ async def api_frame_reset_event(id: int, redis: Redis = Depends(get_redis)):
289290
raise HTTPException(status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail=str(e))
290291

291292

292-
@private_api.post("/frames/{id:int}/restart")
293+
@api_with_auth.post("/frames/{id:int}/restart")
293294
async def api_frame_restart_event(id: int, redis: Redis = Depends(get_redis)):
294295
try:
295296
from app.tasks import restart_frame
@@ -299,7 +300,7 @@ async def api_frame_restart_event(id: int, redis: Redis = Depends(get_redis)):
299300
raise HTTPException(status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail=str(e))
300301

301302

302-
@private_api.post("/frames/{id:int}/stop")
303+
@api_with_auth.post("/frames/{id:int}/stop")
303304
async def api_frame_stop_event(id: int, redis: Redis = Depends(get_redis)):
304305
try:
305306
from app.tasks import stop_frame
@@ -309,7 +310,7 @@ async def api_frame_stop_event(id: int, redis: Redis = Depends(get_redis)):
309310
raise HTTPException(status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail=str(e))
310311

311312

312-
@private_api.post("/frames/{id:int}/deploy")
313+
@api_with_auth.post("/frames/{id:int}/deploy")
313314
async def api_frame_deploy_event(id: int, redis: Redis = Depends(get_redis)):
314315
try:
315316
from app.tasks import deploy_frame
@@ -319,7 +320,7 @@ async def api_frame_deploy_event(id: int, redis: Redis = Depends(get_redis)):
319320
raise HTTPException(status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail=str(e))
320321

321322

322-
@private_api.post("/frames/{id:int}")
323+
@api_with_auth.post("/frames/{id:int}")
323324
async def api_frame_update_endpoint(
324325
id: int,
325326
data: FrameUpdateRequest,
@@ -356,7 +357,7 @@ async def api_frame_update_endpoint(
356357
return {"message": "Frame updated successfully"}
357358

358359

359-
@private_api.post("/frames/new", response_model=FrameResponse)
360+
@api_with_auth.post("/frames/new", response_model=FrameResponse)
360361
async def api_frame_new(data: FrameCreateRequest, db: Session = Depends(get_db), redis: Redis = Depends(get_redis)):
361362
try:
362363
frame = await new_frame(db, redis, data.name, data.frame_host, data.server_host, data.device, data.interval)
@@ -365,7 +366,7 @@ async def api_frame_new(data: FrameCreateRequest, db: Session = Depends(get_db),
365366
raise HTTPException(status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail=str(e))
366367

367368

368-
@private_api.delete("/frames/{frame_id}")
369+
@api_with_auth.delete("/frames/{frame_id}")
369370
async def api_frame_delete(
370371
frame_id: int,
371372
db: Session = Depends(get_db),
@@ -378,7 +379,7 @@ async def api_frame_delete(
378379
raise HTTPException(status_code=404, detail="Frame not found")
379380

380381

381-
@private_api.get("/frames/{id:int}/metrics", response_model=FrameMetricsResponse)
382+
@api_with_auth.get("/frames/{id:int}/metrics", response_model=FrameMetricsResponse)
382383
async def api_frame_metrics(id: int, db: Session = Depends(get_db)):
383384
frame = db.get(Frame, id)
384385
if frame is None:

backend/app/api/log.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@
77
from app.models.log import process_log
88
from app.schemas.log import LogRequest, LogResponse
99
from app.redis import get_redis
10-
from . import public_api
10+
from . import api_public
1111

12-
@public_api.post("/log", response_model=LogResponse)
12+
@api_public.post("/log", response_model=LogResponse)
1313
async def post_api_log(
1414
data: LogRequest,
1515
db: Session = Depends(get_db),

backend/app/api/repositories.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,13 @@
1515
RepositoryResponse,
1616
RepositoriesListResponse
1717
)
18-
from . import private_api
18+
from . import api_with_auth
1919

2020
FRAMEOS_SAMPLES_URL = "https://repo.frameos.net/samples/repository.json"
2121
FRAMEOS_GALLERY_URL = "https://repo.frameos.net/gallery/repository.json"
2222

2323

24-
@private_api.post("/repositories", response_model=RepositoryResponse, status_code=201)
24+
@api_with_auth.post("/repositories", response_model=RepositoryResponse, status_code=201)
2525
async def create_repository(data: RepositoryCreateRequest, db: Session = Depends(get_db)):
2626
url = data.url
2727
if not url:
@@ -41,7 +41,7 @@ async def create_repository(data: RepositoryCreateRequest, db: Session = Depends
4141
logging.error(f'Database error: {e}')
4242
raise HTTPException(status_code=500, detail="Database error")
4343

44-
@private_api.get("/repositories", response_model=RepositoriesListResponse)
44+
@api_with_auth.get("/repositories", response_model=RepositoriesListResponse)
4545
async def get_repositories(db: Session = Depends(get_db)):
4646
try:
4747
# Remove old repo if it exists
@@ -75,7 +75,7 @@ async def get_repositories(db: Session = Depends(get_db)):
7575
logging.error(f'Database error: {e}')
7676
raise HTTPException(status_code=500, detail="Database error")
7777

78-
@private_api.get("/repositories/{repository_id}", response_model=RepositoryResponse)
78+
@api_with_auth.get("/repositories/{repository_id}", response_model=RepositoryResponse)
7979
async def get_repository(repository_id: str, db: Session = Depends(get_db)):
8080
try:
8181
repository = db.get(Repository, repository_id)
@@ -87,7 +87,7 @@ async def get_repository(repository_id: str, db: Session = Depends(get_db)):
8787
logging.error(f'Database error: {e}')
8888
raise HTTPException(status_code=500, detail="Database error")
8989

90-
@private_api.patch("/repositories/{repository_id}", response_model=RepositoryResponse)
90+
@api_with_auth.patch("/repositories/{repository_id}", response_model=RepositoryResponse)
9191
async def update_repository(repository_id: str, data: RepositoryUpdateRequest, db: Session = Depends(get_db)):
9292
try:
9393
repository = db.get(Repository, repository_id)
@@ -106,7 +106,7 @@ async def update_repository(repository_id: str, data: RepositoryUpdateRequest, d
106106
logging.error(f'Database error: {e}')
107107
raise HTTPException(status_code=500, detail="Database error")
108108

109-
@private_api.delete("/repositories/{repository_id}")
109+
@api_with_auth.delete("/repositories/{repository_id}")
110110
async def delete_repository(repository_id: str, db: Session = Depends(get_db)):
111111
try:
112112
repository = db.get(Repository, repository_id)

backend/app/api/settings.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,13 @@
66
from app.database import get_db
77
from app.models.settings import get_settings_dict, Settings
88
from app.schemas.settings import SettingsResponse, SettingsUpdateRequest
9-
from . import private_api
9+
from . import api_with_auth
1010

11-
@private_api.get("/settings", response_model=SettingsResponse)
11+
@api_with_auth.get("/settings", response_model=SettingsResponse)
1212
async def get_settings(db: Session = Depends(get_db)):
1313
return get_settings_dict(db)
1414

15-
@private_api.post("/settings", response_model=SettingsResponse)
15+
@api_with_auth.post("/settings", response_model=SettingsResponse)
1616
async def set_settings(data: SettingsUpdateRequest, db: Session = Depends(get_db)):
1717
payload = data.to_dict()
1818
if not payload:

backend/app/api/ssh.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
from fastapi import HTTPException
22
from cryptography.hazmat.primitives.asymmetric import rsa
33
from cryptography.hazmat.primitives import serialization
4-
from . import private_api
4+
from . import api_with_auth
55
from app.schemas.ssh import SSHKeyResponse
66

7-
@private_api.post("/generate_ssh_keys", response_model=SSHKeyResponse)
7+
@api_with_auth.post("/generate_ssh_keys", response_model=SSHKeyResponse)
88
async def generate_ssh_keys():
99
try:
1010
private_key = rsa.generate_private_key(

0 commit comments

Comments
 (0)