Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 3 additions & 4 deletions apps/backend/app/api/routes/api_client/api.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
from fastapi import APIRouter, BackgroundTasks, Depends, Query

from app.api.routes.auth.services import get_current_uid
from app.api.routes.api_client import collections_delta
from app.api.routes.api_client import services as api_client_svc
from app.api.routes.api_client.schema import (
HISTORY_MAX_ITEMS,
ApiClientCollectionCreate,
ApiClientCollectionOut,
ApiClientCollectionUpdate,
Expand All @@ -11,10 +12,8 @@
ApiClientEnvironmentUpdate,
ApiClientHistoryCreate,
ApiClientHistoryOut,
HISTORY_MAX_ITEMS,
)
from app.api.routes.api_client import collections_delta

from app.api.routes.auth.services import get_current_uid

router = APIRouter(prefix="/api-client", tags=["api-client"])
router.include_router(collections_delta.router)
Expand Down
4 changes: 2 additions & 2 deletions apps/backend/app/api/routes/api_client/collections_delta.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,17 @@
from pymongo import ReturnDocument
from pymongo.errors import PyMongoError

from app.api.routes.auth.services import get_current_uid
from app.api.routes.api_client.schema import (
AddItemOp,
ApiClientCollectionOut,
ApplyDeltaRequest,
ApplyDeltaResponse,
ApiClientCollectionOut,
DeleteItemOp,
MoveItemOp,
Op,
UpdateItemOp,
)
from app.api.routes.auth.services import get_current_uid
from app.core.cache import bump_version
from app.database import db_manager
from app.utils.collection_name import API_CLIENT_COLLECTIONS
Expand Down
20 changes: 10 additions & 10 deletions apps/backend/app/api/routes/api_client/schema.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from typing import Annotated, Any, Literal, Optional, Union
from typing import Annotated, Any, Literal

from pydantic import BaseModel, ConfigDict, Field

Expand All @@ -15,8 +15,8 @@ class ApiClientCollectionCreate(BaseModel):
class ApiClientCollectionUpdate(BaseModel):
model_config = ConfigDict(extra="ignore")

name: Optional[str] = Field(default=None, min_length=1)
items: Optional[list[dict[str, Any]]] = None
name: str | None = Field(default=None, min_length=1)
items: list[dict[str, Any]] | None = None


class ApiClientCollectionOut(ApiClientCollectionBase):
Expand All @@ -37,8 +37,8 @@ class ApiClientEnvironmentCreate(BaseModel):
class ApiClientEnvironmentUpdate(BaseModel):
model_config = ConfigDict(extra="ignore")

name: Optional[str] = Field(default=None, min_length=1)
variables: Optional[list[dict[str, Any]]] = None
name: str | None = Field(default=None, min_length=1)
variables: list[dict[str, Any]] | None = None


class ApiClientEnvironmentOut(ApiClientEnvironmentBase):
Expand All @@ -56,7 +56,7 @@ class AddItemOp(BaseModel):
type: Literal["add"]
parent_id: str
item: dict[str, Any]
position: Optional[int] = None
position: int | None = None


class UpdateItemOp(BaseModel):
Expand All @@ -78,7 +78,7 @@ class MoveItemOp(BaseModel):


Op = Annotated[
Union[AddItemOp, UpdateItemOp, DeleteItemOp, MoveItemOp],
AddItemOp | UpdateItemOp | DeleteItemOp | MoveItemOp,
Field(discriminator="type"),
]

Expand All @@ -103,8 +103,8 @@ class ApiClientHistoryCreate(BaseModel):
body: dict[str, Any] = Field(default_factory=dict)
auth: dict[str, Any] = Field(default_factory=dict)
name: str = Field(min_length=1)
status: Optional[int] = None
timestamp: Optional[int] = None
status: int | None = None
timestamp: int | None = None


class ApiClientHistoryOut(BaseModel):
Expand All @@ -119,5 +119,5 @@ class ApiClientHistoryOut(BaseModel):
auth: dict[str, Any]
name: str
timestamp: int
status: Optional[int] = None
status: int | None = None

84 changes: 20 additions & 64 deletions apps/backend/app/api/routes/api_client/services.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,10 @@
from bson import ObjectId
from bson.errors import InvalidId
from fastapi import HTTPException, status
from pymongo import ReturnDocument
from pymongo.errors import PyMongoError

from app.utils.collection_name import (
API_CLIENT_COLLECTIONS,
API_CLIENT_ENVIRONMENTS,
API_CLIENT_HISTORY,
)
from app.api.routes.api_client.schema import (
HISTORY_MAX_ITEMS,
ApiClientCollectionCreate,
ApiClientCollectionOut,
ApiClientCollectionUpdate,
Expand All @@ -21,10 +16,15 @@
ApiClientEnvironmentUpdate,
ApiClientHistoryCreate,
ApiClientHistoryOut,
HISTORY_MAX_ITEMS,
)
from app.core.cache import cached, bump_version
from app.core.cache import bump_version, cached
from app.database import db_manager
from app.utils.collection_name import (
API_CLIENT_COLLECTIONS,
API_CLIENT_ENVIRONMENTS,
API_CLIENT_HISTORY,
)
from app.utils.crud import safe_delete_one, safe_insert, safe_update_one

HISTORY_TRIM_BATCH_SIZE = 500

Expand Down Expand Up @@ -70,13 +70,7 @@ async def list_collections(*, uid: str) -> list[ApiClientCollectionOut]:

async def create_collection(uid: str, body: ApiClientCollectionCreate) -> ApiClientCollectionOut:
doc: dict[str, Any] = {"created_by": uid, "name": body.name, "items": []}
try:
result = await db_manager.insert_one(API_CLIENT_COLLECTIONS, doc)
except PyMongoError as exc:
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Failed to create collection."
) from exc
doc["_id"] = result.inserted_id
await safe_insert(API_CLIENT_COLLECTIONS, doc, name="Collection")
await bump_version(ns="api_client", uid=uid)
return _collection_to_out(doc)

Expand All @@ -89,28 +83,16 @@ async def patch_collection(uid: str, collection_id: str, body: ApiClientCollecti
if not doc:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Collection not found.")
return _collection_to_out(doc)
try:
doc = await db_manager.find_one_and_update(
API_CLIENT_COLLECTIONS,
{"_id": oid, "created_by": uid},
{"$set": patch},
return_document=ReturnDocument.AFTER,
)
except PyMongoError as exc:
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Failed to update collection."
) from exc
if not doc:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Collection not found.")
doc = await safe_update_one(
API_CLIENT_COLLECTIONS, {"_id": oid, "created_by": uid}, patch, name="Collection"
)
await bump_version(ns="api_client", uid=uid)
return _collection_to_out(doc)


async def delete_collection(uid: str, collection_id: str) -> None:
oid = _parse_oid(collection_id, kind="collection")
result = await db_manager.delete_one(API_CLIENT_COLLECTIONS, {"_id": oid, "created_by": uid})
if result.deleted_count == 0:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Collection not found.")
await safe_delete_one(API_CLIENT_COLLECTIONS, {"_id": oid, "created_by": uid}, name="Collection")
await bump_version(ns="api_client", uid=uid)


Expand All @@ -127,13 +109,7 @@ async def list_environments(*, uid: str) -> list[ApiClientEnvironmentOut]:

async def create_environment(uid: str, body: ApiClientEnvironmentCreate) -> ApiClientEnvironmentOut:
doc: dict[str, Any] = {"created_by": uid, "name": body.name, "variables": []}
try:
result = await db_manager.insert_one(API_CLIENT_ENVIRONMENTS, doc)
except PyMongoError as exc:
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Failed to create environment."
) from exc
doc["_id"] = result.inserted_id
await safe_insert(API_CLIENT_ENVIRONMENTS, doc, name="Environment")
await bump_version(ns="api_client", uid=uid)
return _env_to_out(doc)

Expand All @@ -146,28 +122,16 @@ async def patch_environment(uid: str, environment_id: str, body: ApiClientEnviro
if not doc:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Environment not found.")
return _env_to_out(doc)
try:
doc = await db_manager.find_one_and_update(
API_CLIENT_ENVIRONMENTS,
{"_id": oid, "created_by": uid},
{"$set": patch},
return_document=ReturnDocument.AFTER,
)
except PyMongoError as exc:
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Failed to update environment."
) from exc
if not doc:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Environment not found.")
doc = await safe_update_one(
API_CLIENT_ENVIRONMENTS, {"_id": oid, "created_by": uid}, patch, name="Environment"
)
await bump_version(ns="api_client", uid=uid)
return _env_to_out(doc)


async def delete_environment(uid: str, environment_id: str) -> None:
oid = _parse_oid(environment_id, kind="environment")
result = await db_manager.delete_one(API_CLIENT_ENVIRONMENTS, {"_id": oid, "created_by": uid})
if result.deleted_count == 0:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Environment not found.")
await safe_delete_one(API_CLIENT_ENVIRONMENTS, {"_id": oid, "created_by": uid}, name="Environment")
await bump_version(ns="api_client", uid=uid)


Expand Down Expand Up @@ -228,22 +192,14 @@ async def create_history(uid: str, body: ApiClientHistoryCreate) -> ApiClientHis
"timestamp": ts,
"status": body.status,
}
try:
result = await db_manager.insert_one(API_CLIENT_HISTORY, doc)
except PyMongoError as exc:
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Failed to save history entry."
) from exc
doc["_id"] = result.inserted_id
await safe_insert(API_CLIENT_HISTORY, doc, name="History entry")
await bump_version(ns="api_client", uid=uid)
return _history_doc_to_out(doc)


async def delete_history_entry(uid: str, entry_id: str) -> None:
oid = _parse_oid(entry_id, kind="history")
result = await db_manager.delete_one(API_CLIENT_HISTORY, {"_id": oid, "created_by": uid})
if result.deleted_count == 0:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="History entry not found.")
await safe_delete_one(API_CLIENT_HISTORY, {"_id": oid, "created_by": uid}, name="History entry")
await bump_version(ns="api_client", uid=uid)


Expand Down
9 changes: 4 additions & 5 deletions apps/backend/app/api/routes/bookmarks/api.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
from fastapi import APIRouter, Depends, Query

from typing import Optional
from fastapi import APIRouter, Depends, Query

from app.api.routes.auth.services import get_current_uid
from app.api.routes.bookmarks import services as bm_svc
Expand Down Expand Up @@ -46,12 +45,12 @@ async def clear_all(uid: str = Depends(get_current_uid)) -> dict[str, int]:
@bookmarks_router.get("", response_model=list[BookmarkOut], summary="List bookmarks")
async def list_bookmarks(
uid: str = Depends(get_current_uid),
folder_id: Optional[str] = Query(
folder_id: str | None = Query(
default=None,
alias="folderId",
),
skip: int = Query(default=0, ge=0),
limit: Optional[int] = Query(default=None, ge=1, le=500),
limit: int | None = Query(default=None, ge=1, le=500),
) -> list[BookmarkOut]:
return await bm_svc.list_bookmarks(uid=uid, folder_id=folder_id, skip=skip, limit=limit)

Expand Down Expand Up @@ -106,7 +105,7 @@ async def remove_bookmark(
async def list_folders(
uid: str = Depends(get_current_uid),
skip: int = Query(default=0, ge=0),
limit: Optional[int] = Query(default=None, ge=1, le=500),
limit: int | None = Query(default=None, ge=1, le=500),
) -> list[BookmarkFolderOut]:
return await bm_svc.list_folders(uid=uid, skip=skip, limit=limit)

Expand Down
43 changes: 21 additions & 22 deletions apps/backend/app/api/routes/bookmarks/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
db.bookmarkFolders.create_index([("created_by", 1), ("createdAt", 1)])
"""

from typing import Any, Optional

from pydantic import BaseModel, ConfigDict, Field

Expand All @@ -36,31 +35,31 @@ class BookmarkBase(BaseModel):

title: str = Field(min_length=1)
url: str = Field(min_length=1)
description: Optional[str] = None
favicon: Optional[str] = None
description: str | None = None
favicon: str | None = None
tags: list[str] = Field(default_factory=list)
folderId: Optional[str] = None
folderId: str | None = None


class BookmarkCreate(BookmarkBase):
"""Optional ``id`` — if omitted, server generates one (client uses timestamp-random)."""

id: Optional[str] = None
id: str | None = None


class BookmarkUpdate(BaseModel):
model_config = ConfigDict(extra="ignore")

title: Optional[str] = Field(default=None, min_length=1)
url: Optional[str] = Field(default=None, min_length=1)
description: Optional[str] = None
favicon: Optional[str] = None
tags: Optional[list[str]] = None
folderId: Optional[str] = None
title: str | None = Field(default=None, min_length=1)
url: str | None = Field(default=None, min_length=1)
description: str | None = None
favicon: str | None = None
tags: list[str] | None = None
folderId: str | None = None


class BookmarkMove(BaseModel):
folderId: Optional[str] = None
folderId: str | None = None


class BookmarkOut(BookmarkBase):
Expand All @@ -73,24 +72,24 @@ class BookmarkFolderBase(BaseModel):
model_config = ConfigDict(extra="ignore")

name: str = Field(min_length=1)
parentId: Optional[str] = None
color: Optional[str] = None
icon: Optional[str] = None
isExpanded: Optional[bool] = None
parentId: str | None = None
color: str | None = None
icon: str | None = None
isExpanded: bool | None = None


class BookmarkFolderCreate(BookmarkFolderBase):
id: Optional[str] = None
id: str | None = None


class BookmarkFolderUpdate(BaseModel):
model_config = ConfigDict(extra="ignore")

name: Optional[str] = Field(default=None, min_length=1)
parentId: Optional[str] = None
color: Optional[str] = None
icon: Optional[str] = None
isExpanded: Optional[bool] = None
name: str | None = Field(default=None, min_length=1)
parentId: str | None = None
color: str | None = None
icon: str | None = None
isExpanded: bool | None = None


class BookmarkFolderExpanded(BaseModel):
Expand Down
Loading