feat(native-connector): added google maps places & reviews scrapers

This commit is contained in:
DESKTOP-RTLN3BA\$punk 2026-07-02 21:58:24 -07:00
parent cc99bc4cae
commit 7185079bd6
114 changed files with 4688 additions and 425 deletions

View file

@ -46,9 +46,9 @@ from app.db import (
NewChatMessage,
NewChatMessageRole,
NewChatThread,
Workspace,
TokenUsage,
User,
Workspace,
)
from app.routes import new_chat_routes
from app.services.token_tracking_service import TurnTokenAccumulator

View file

@ -48,8 +48,8 @@ from app.db import (
NewChatMessage,
NewChatMessageRole,
NewChatThread,
Workspace,
User,
Workspace,
)
from app.services.new_streaming_service import VercelStreamingService
from app.tasks.chat import persistence as persistence_module

View file

@ -38,9 +38,9 @@ from app.db import (
NewChatMessage,
NewChatMessageRole,
NewChatThread,
Workspace,
TokenUsage,
User,
Workspace,
)
from app.services.token_tracking_service import TurnTokenAccumulator
from app.tasks.chat import persistence as persistence_module

View file

@ -19,10 +19,10 @@ from sqlalchemy.ext.asyncio import AsyncSession
from app.auth.context import AuthContext
from app.db import (
ChatVisibility,
User,
Workspace,
WorkspaceMembership,
WorkspaceRole,
User,
)
from app.routes import new_chat_routes
from app.schemas.new_chat import (

View file

@ -9,8 +9,8 @@ from app.config import config
from app.db import (
SearchSourceConnector,
SearchSourceConnectorType,
Workspace,
User,
Workspace,
)
from app.utils.oauth_security import OAuthStateManager
@ -57,8 +57,7 @@ async def test_callback_with_error_param_redirects_to_denied_page(
assert response.status_code in {302, 303, 307}
location = response.headers["location"]
assert (
f"/dashboard/{db_workspace.id}/connectors/callback?"
"error=composio_oauth_denied"
f"/dashboard/{db_workspace.id}/connectors/callback?error=composio_oauth_denied"
) in location
connectors = await _drive_connectors(

View file

@ -320,9 +320,7 @@ class TestDocumentSearchability:
doc_ids = resp.json()["document_ids"]
cleanup_doc_ids.extend(doc_ids)
await poll_document_status(
client, headers, doc_ids, workspace_id=workspace_id
)
await poll_document_status(client, headers, doc_ids, workspace_id=workspace_id)
search_resp = await client.get(
"/api/v1/documents/search",

View file

@ -219,9 +219,7 @@ class TestDocumentProcessingNotification:
doc_ids = resp.json()["document_ids"]
cleanup_doc_ids.extend(doc_ids)
await poll_document_status(
client, headers, doc_ids, workspace_id=workspace_id
)
await poll_document_status(client, headers, doc_ids, workspace_id=workspace_id)
notifications = await get_notifications(
client,

View file

@ -18,8 +18,8 @@ from app.db import (
DocumentType,
SearchSourceConnector,
SearchSourceConnectorType,
Workspace,
User,
Workspace,
)
EMBEDDING_DIM = app_config.embedding_model_instance.dimension
@ -275,9 +275,7 @@ async def seed_connector(
session.add(user)
await session.flush()
space = Workspace(
name=f"{name_prefix} {uuid.uuid4().hex[:6]}", user_id=user.id
)
space = Workspace(name=f"{name_prefix} {uuid.uuid4().hex[:6]}", user_id=user.id)
session.add(space)
await session.flush()
space_id = space.id

View file

@ -119,9 +119,7 @@ async def test_reindex_updates_content(db_session, db_workspace, db_user, mocker
@pytest.mark.usefixtures("patched_embed_texts", "patched_chunk_text")
async def test_reindex_updates_content_hash(
db_session, db_workspace, db_user, mocker
):
async def test_reindex_updates_content_hash(db_session, db_workspace, db_user, mocker):
"""Content hash is recomputed after reindexing with new source markdown."""
adapter = UploadDocumentAdapter(db_session)
await adapter.index(

View file

@ -55,9 +55,7 @@ async def test_edit_keeps_unchanged_rows_and_embeds_only_the_new_text(
patched_embed_texts,
):
service = IndexingPipelineService(session=db_session)
doc_v1 = make_connector_document(
workspace_id=db_workspace.id, source_markdown=_V1
)
doc_v1 = make_connector_document(workspace_id=db_workspace.id, source_markdown=_V1)
document = await _index(service, doc_v1)
ids_v1 = {c.content: c.id for c in await _load_chunks(db_session, document.id)}
@ -100,9 +98,7 @@ async def test_head_insert_shifts_positions_without_new_rows_for_old_text(
service = IndexingPipelineService(session=db_session)
document = await _index(
service,
make_connector_document(
workspace_id=db_workspace.id, source_markdown=_V1
),
make_connector_document(workspace_id=db_workspace.id, source_markdown=_V1),
)
ids_v1 = {c.content: c.id for c in await _load_chunks(db_session, document.id)}
@ -136,9 +132,7 @@ async def test_removed_paragraph_is_deleted_and_order_compacts(
service = IndexingPipelineService(session=db_session)
document = await _index(
service,
make_connector_document(
workspace_id=db_workspace.id, source_markdown=_V1
),
make_connector_document(workspace_id=db_workspace.id, source_markdown=_V1),
)
ids_v1 = {c.content: c.id for c in await _load_chunks(db_session, document.id)}
@ -171,9 +165,7 @@ async def test_kill_switch_falls_back_to_full_replace(
service = IndexingPipelineService(session=db_session)
document = await _index(
service,
make_connector_document(
workspace_id=db_workspace.id, source_markdown=_V1
),
make_connector_document(workspace_id=db_workspace.id, source_markdown=_V1),
)
ids_v1 = {c.id for c in await _load_chunks(db_session, document.id)}

View file

@ -14,8 +14,8 @@ from app.db import (
DocumentType,
DocumentVersion,
Folder,
Workspace,
User,
Workspace,
)
pytestmark = pytest.mark.integration

View file

@ -59,9 +59,7 @@ async def test_legacy_composio_gmail_doc_migrated_in_db(
assert reloaded.document_type == DocumentType.GOOGLE_GMAIL_CONNECTOR
async def test_no_legacy_doc_is_noop(
db_session, db_workspace, make_connector_document
):
async def test_no_legacy_doc_is_noop(db_session, db_workspace, make_connector_document):
"""When no legacy document exists, migrate_legacy_docs does nothing."""
connector_doc = make_connector_document(
document_type=DocumentType.GOOGLE_CALENDAR_CONNECTOR,

View file

@ -225,9 +225,7 @@ async def test_updated_at_advances_when_title_only_changes(
db_session, db_workspace, make_connector_document
):
"""updated_at advances even when only the title changes."""
original = make_connector_document(
workspace_id=db_workspace.id, title="Old Title"
)
original = make_connector_document(workspace_id=db_workspace.id, title="Old Title")
service = IndexingPipelineService(session=db_session)
first = await service.prepare_for_indexing([original])
@ -238,9 +236,7 @@ async def test_updated_at_advances_when_title_only_changes(
)
updated_at_v1 = result.scalars().first().updated_at
renamed = make_connector_document(
workspace_id=db_workspace.id, title="New Title"
)
renamed = make_connector_document(workspace_id=db_workspace.id, title="New Title")
await service.prepare_for_indexing([renamed])
result = await db_session.execute(

View file

@ -10,7 +10,7 @@ import pytest
from sqlalchemy import func, select
from sqlalchemy.ext.asyncio import AsyncSession
from app.db import Workspace, User
from app.db import User, Workspace
from app.notifications.persistence import Notification
from app.notifications.service import NotificationService

View file

@ -5,7 +5,7 @@ from __future__ import annotations
import pytest
from sqlalchemy.ext.asyncio import AsyncSession
from app.db import Workspace, User
from app.db import User, Workspace
from app.notifications.service import NotificationService
pytestmark = pytest.mark.integration
@ -47,9 +47,7 @@ async def test_comment_reply_truncates_long_preview(
db_session: AsyncSession, db_user: User, db_workspace: Workspace
):
"""A long comment preview is truncated in the reply message."""
notification = await _notify(
db_session, db_user, db_workspace, preview="y" * 150
)
notification = await _notify(db_session, db_user, db_workspace, preview="y" * 150)
assert notification.message == "y" * 100 + "..."

View file

@ -10,7 +10,7 @@ from __future__ import annotations
import pytest
from sqlalchemy.ext.asyncio import AsyncSession
from app.db import Workspace, User
from app.db import User, Workspace
from app.notifications.service import NotificationService
pytestmark = pytest.mark.integration

View file

@ -5,7 +5,7 @@ from __future__ import annotations
import pytest
from sqlalchemy.ext.asyncio import AsyncSession
from app.db import Workspace, User
from app.db import User, Workspace
from app.notifications.service import NotificationService
pytestmark = pytest.mark.integration

View file

@ -5,7 +5,7 @@ from __future__ import annotations
import pytest
from sqlalchemy.ext.asyncio import AsyncSession
from app.db import Workspace, User
from app.db import User, Workspace
from app.notifications.service import NotificationService
pytestmark = pytest.mark.integration

View file

@ -5,7 +5,7 @@ from __future__ import annotations
import pytest
from sqlalchemy.ext.asyncio import AsyncSession
from app.db import Workspace, User
from app.db import User, Workspace
from app.notifications.service import NotificationService
pytestmark = pytest.mark.integration
@ -47,9 +47,7 @@ async def test_new_mention_truncates_long_preview(
db_session: AsyncSession, db_user: User, db_workspace: Workspace
):
"""A long comment preview is truncated in the mention message."""
notification = await _notify(
db_session, db_user, db_workspace, preview="x" * 150
)
notification = await _notify(db_session, db_user, db_workspace, preview="x" * 150)
assert notification.message == "x" * 100 + "..."

View file

@ -26,7 +26,7 @@ from sqlalchemy.ext.asyncio import AsyncSession
from app.app import app, limiter
from app.auth.context import AuthContext
from app.config import config as app_config
from app.db import Workspace, User, get_async_session
from app.db import User, Workspace, get_async_session
from app.podcasts.persistence import Podcast, PodcastStatus
from app.podcasts.schemas import (
DurationTarget,

View file

@ -38,9 +38,7 @@ async def test_cancel_from_a_terminal_state_conflicts(
assert resp.status_code == 409
async def test_cancel_of_a_regeneration_is_rejected(
client, db_workspace, make_podcast
):
async def test_cancel_of_a_regeneration_is_rejected(client, db_workspace, make_podcast):
# Cancelling here would destroy a playable episode; reverting the
# regeneration is the way back.
podcast = await make_podcast(

View file

@ -9,7 +9,7 @@ import pytest_asyncio
from sqlalchemy.ext.asyncio import AsyncSession
from app.config import config as app_config
from app.db import Chunk, Document, DocumentType, Workspace, User
from app.db import Chunk, Document, DocumentType, User, Workspace
EMBEDDING_DIM = app_config.embedding_model_instance.dimension
DUMMY_EMBEDDING = [0.1] * EMBEDDING_DIM

View file

@ -27,8 +27,8 @@ from app.auth.context import AuthContext
from app.db import (
SearchSourceConnector,
SearchSourceConnectorType,
Workspace,
User,
Workspace,
)
from app.routes.search_source_connectors_routes import index_connector_content
from app.routes.workspaces_routes import create_default_roles_and_membership

View file

@ -7,7 +7,7 @@ import pytest_asyncio
from sqlalchemy import func, select
from sqlalchemy.ext.asyncio import AsyncSession
from app.db import Document, DocumentType, DocumentVersion, Workspace, User
from app.db import Document, DocumentType, DocumentVersion, User, Workspace
pytestmark = pytest.mark.integration

View file

@ -32,8 +32,8 @@ from app.auth.context import AuthContext
from app.db import (
SearchSourceConnector,
SearchSourceConnectorType,
Workspace,
User,
Workspace,
)
from app.routes.obsidian_plugin_routes import (
obsidian_connect,

View file

@ -7,7 +7,7 @@ from fastapi import HTTPException
from sqlalchemy.ext.asyncio import AsyncSession
from app.auth.context import AuthContext
from app.db import PersonalAccessToken, Workspace, User
from app.db import PersonalAccessToken, User, Workspace
from app.users import allow_any_principal, require_session_context
from app.utils.rbac import check_workspace_access

View file

@ -7,7 +7,7 @@ from fastapi import HTTPException
from sqlalchemy.ext.asyncio import AsyncSession
from app.auth.context import AuthContext
from app.db import PersonalAccessToken, Workspace, User
from app.db import PersonalAccessToken, User, Workspace
from app.routes.workspaces_routes import create_default_roles_and_membership
from app.utils.rbac import check_workspace_access, get_allowed_read_space_ids