Refactor: API connectors (#14228)

### What problem does this PR solve?

Refactor /api/v1/connectors to be more RESTful.

### Type of change
- [x] Refactoring
This commit is contained in:
Wang Qi
2026-04-22 20:42:41 +08:00
committed by GitHub
parent c08cd8e090
commit 01753b8f31
9 changed files with 66 additions and 44 deletions

View File

@@ -35,15 +35,30 @@ from rag.utils.redis_conn import REDIS_CONN
from api.apps import login_required, current_user
from box_sdk_gen import BoxOAuth, OAuthConfig, GetAuthorizeUrlOptions
@manager.route("/set", methods=["POST"]) # noqa: F821
@manager.route("/connectors/<connector_id>", methods=["PATCH"]) # noqa: F821
@login_required
async def set_connector():
async def update_connector(connector_id):
req = await get_request_json()
if req.get("id"):
e, conn = ConnectorService.get_by_id(connector_id)
if not e:
return get_data_error_result(message="Can't find this Connector!")
if req:
conn = {fld: req[fld] for fld in ["prune_freq", "refresh_freq", "config", "timeout_secs"] if fld in req}
ConnectorService.update_by_id(req["id"], conn)
else:
conn["id"] = connector_id
ConnectorService.update_by_id(connector_id, conn)
await asyncio.sleep(1)
e, conn = ConnectorService.get_by_id(connector_id)
return get_json_result(data=conn.to_dict())
@manager.route("/connectors", methods=["POST"]) # noqa: F821
@login_required
async def create_connector():
req = await get_request_json()
if req:
req["id"] = get_uuid()
conn = {
"id": req["id"],
@@ -65,13 +80,13 @@ async def set_connector():
return get_json_result(data=conn.to_dict())
@manager.route("/list", methods=["GET"]) # noqa: F821
@manager.route("/connectors", methods=["GET"]) # noqa: F821
@login_required
def list_connector():
return get_json_result(data=ConnectorService.list(current_user.id))
@manager.route("/<connector_id>", methods=["GET"]) # noqa: F821
@manager.route("/connectors/<connector_id>", methods=["GET"]) # noqa: F821
@login_required
def get_connector(connector_id):
e, conn = ConnectorService.get_by_id(connector_id)
@@ -80,7 +95,7 @@ def get_connector(connector_id):
return get_json_result(data=conn.to_dict())
@manager.route("/<connector_id>/logs", methods=["GET"]) # noqa: F821
@manager.route("/connectors/<connector_id>/logs", methods=["GET"]) # noqa: F821
@login_required
def list_logs(connector_id):
req = request.args.to_dict(flat=True)
@@ -88,7 +103,7 @@ def list_logs(connector_id):
return get_json_result(data={"total": total, "logs": arr})
@manager.route("/<connector_id>/resume", methods=["PUT"]) # noqa: F821
@manager.route("/connectors/<connector_id>/resume", methods=["POST"]) # noqa: F821
@login_required
async def resume(connector_id):
req = await get_request_json()
@@ -99,7 +114,7 @@ async def resume(connector_id):
return get_json_result(data=True)
@manager.route("/<connector_id>/rebuild", methods=["PUT"]) # noqa: F821
@manager.route("/connectors/<connector_id>/rebuild", methods=["POST"]) # noqa: F821
@login_required
@validate_request("kb_id")
async def rebuild(connector_id):
@@ -110,7 +125,7 @@ async def rebuild(connector_id):
return get_json_result(data=True)
@manager.route("/<connector_id>/rm", methods=["POST"]) # noqa: F821
@manager.route("/connectors/<connector_id>", methods=["DELETE"]) # noqa: F821
@login_required
def rm_connector(connector_id):
ConnectorService.resume(connector_id, TaskStatus.CANCEL)
@@ -185,7 +200,7 @@ async def _render_web_oauth_popup(flow_id: str, success: bool, message: str, sou
return response
@manager.route("/google/oauth/web/start", methods=["POST"]) # noqa: F821
@manager.route("/connectors/google/oauth/web/start", methods=["POST"]) # noqa: F821
@login_required
@validate_request("credentials")
async def start_google_web_oauth():
@@ -265,7 +280,7 @@ async def start_google_web_oauth():
)
@manager.route("/gmail/oauth/web/callback", methods=["GET"]) # noqa: F821
@manager.route("/connectors/gmail/oauth/web/callback", methods=["GET"]) # noqa: F821
async def google_gmail_web_oauth_callback():
state_id = request.args.get("state")
error = request.args.get("error")
@@ -316,7 +331,7 @@ async def google_gmail_web_oauth_callback():
return await _render_web_oauth_popup(state_id, True, "Authorization completed successfully.", source)
@manager.route("/google-drive/oauth/web/callback", methods=["GET"]) # noqa: F821
@manager.route("/connectors/google-drive/oauth/web/callback", methods=["GET"]) # noqa: F821
async def google_drive_web_oauth_callback():
state_id = request.args.get("state")
error = request.args.get("error")
@@ -366,7 +381,7 @@ async def google_drive_web_oauth_callback():
return await _render_web_oauth_popup(state_id, True, "Authorization completed successfully.", source)
@manager.route("/google/oauth/web/result", methods=["POST"]) # noqa: F821
@manager.route("/connectors/google/oauth/web/result", methods=["POST"]) # noqa: F821
@login_required
@validate_request("flow_id")
async def poll_google_web_result():
@@ -386,7 +401,7 @@ async def poll_google_web_result():
REDIS_CONN.delete(_web_result_cache_key(flow_id, source))
return get_json_result(data={"credentials": result.get("credentials")})
@manager.route("/box/oauth/web/start", methods=["POST"]) # noqa: F821
@manager.route("/connectors/box/oauth/web/start", methods=["POST"]) # noqa: F821
@login_required
async def start_box_web_oauth():
req = await get_request_json()
@@ -429,7 +444,7 @@ async def start_box_web_oauth():
"expires_in": WEB_FLOW_TTL_SECS,}
)
@manager.route("/box/oauth/web/callback", methods=["GET"]) # noqa: F821
@manager.route("/connectors/box/oauth/web/callback", methods=["GET"]) # noqa: F821
async def box_web_oauth_callback():
flow_id = request.args.get("state")
if not flow_id:
@@ -471,7 +486,7 @@ async def box_web_oauth_callback():
return await _render_web_oauth_popup(flow_id, True, "Authorization completed successfully.", "box")
@manager.route("/box/oauth/web/result", methods=["POST"]) # noqa: F821
@manager.route("/connectors/box/oauth/web/result", methods=["POST"]) # noqa: F821
@login_required
@validate_request("flow_id")
async def poll_box_web_result():

View File

@@ -44,7 +44,7 @@ You need to configure the OAuth Consent Screen because it is the step where you
2. Select **Web Application** as **Application type** for the created project:
![](https://github.com/infiniflow/ragflow-docs/blob/040e4acd4c1eac6dc73dc44e934a6518de78d097/images/google_drive/image7.png?raw=true)
3. Enter a client name.
4. Add `http://localhost:9380/v1/connector/google-drive/oauth/web/callback` as **Authorised redirect URIs**:
4. Add `http://localhost:9380/api/v1/connectors/google-drive/oauth/web/callback` as **Authorised redirect URIs**:
5. Add **Authorised JavaScript origins**:
- If deploying RAGFlow from Docker, use `http://localhost:80`:
![](https://github.com/infiniflow/ragflow-docs/blob/040e4acd4c1eac6dc73dc44e934a6518de78d097/images/google_drive/image8.png?raw=true)

View File

@@ -20,7 +20,7 @@ import requests
from configs import HOST_ADDRESS, VERSION
CONNECTOR_BASE_URL = f"{HOST_ADDRESS}/{VERSION}/connector"
CONNECTOR_BASE_URL = f"{HOST_ADDRESS}/api/{VERSION}/connectors"
LLM_API_KEY_URL = f"{HOST_ADDRESS}/{VERSION}/llm/set_api_key"
LANGFUSE_API_KEY_URL = f"{HOST_ADDRESS}/{VERSION}/langfuse/api_key"

View File

@@ -321,7 +321,7 @@ def _load_connector_app(monkeypatch):
box_mod.GetAuthorizeUrlOptions = _GetAuthorizeUrlOptions
monkeypatch.setitem(sys.modules, "box_sdk_gen", box_mod)
module_path = repo_root / "api" / "apps" / "connector_app.py"
module_path = repo_root / "api" / "apps" / "restful_apis" / "connector_api.py"
spec = importlib.util.spec_from_file_location("test_connector_routes_unit", module_path)
module = importlib.util.module_from_spec(spec)
module.manager = _DummyManager()
@@ -363,8 +363,8 @@ def test_connector_basic_routes_and_task_controls(monkeypatch):
"get_request_json",
lambda: _AwaitableValue({"id": "conn-1", "refresh_freq": 7, "config": {"x": 1}}),
)
res = _run(module.set_connector())
assert update_calls == [("conn-1", {"refresh_freq": 7, "config": {"x": 1}})]
res = _run(module.update_connector("conn-1"))
assert update_calls == [("conn-1", {'id': 'conn-1', "refresh_freq": 7, "config": {"x": 1}})]
assert res["data"]["id"] == "conn-1"
monkeypatch.setattr(
@@ -372,7 +372,7 @@ def test_connector_basic_routes_and_task_controls(monkeypatch):
"get_request_json",
lambda: _AwaitableValue({"name": "new", "source": "gmail", "config": {"y": 2}}),
)
res = _run(module.set_connector())
res = _run(module.create_connector())
assert save_calls[-1]["id"] == "generated-id"
assert save_calls[-1]["tenant_id"] == "tenant-1"
assert save_calls[-1]["input_type"] == module.InputType.POLL

View File

@@ -144,7 +144,7 @@ const SourceDetailPage = () => {
];
}, [detail, runSchedule]);
const { addLoading, handleAddOk } = useAddDataSource();
const { addLoading, handleAddOk } = useAddDataSource({isEdit:true});
const onSubmit = useCallback(() => {
formRef?.current?.submit();

View File

@@ -3,7 +3,7 @@ import { useSetModalState } from '@/hooks/common-hooks';
import { useGetPaginationWithRouter } from '@/hooks/logic-hooks';
import dataSourceService, {
dataSourceRebuild,
dataSourceResume,
dataSourceResume, dataSourceUpdate,
deleteDataSource,
featchDataSourceDetail,
getDataSourceLogs,
@@ -68,7 +68,7 @@ export const useListDataSource = () => {
return { list, categorizedList: updatedDataSourceTemplates, isFetching };
};
export const useAddDataSource = () => {
export const useAddDataSource = ({isEdit=false}:{isEdit?:boolean} ) => {
const [addSource, setAddSource] = useState<IDataSorceInfo | undefined>(
undefined,
);
@@ -90,7 +90,9 @@ export const useAddDataSource = () => {
const handleAddOk = useCallback(
async (data: any) => {
setAddLoading(true);
const { data: res } = await dataSourceService.dataSourceSet(data);
const { data: res } = isEdit
? await dataSourceUpdate(data.id, data)
: await dataSourceService.dataSourceSet(data);
console.log('🚀 ~ handleAddOk ~ code:', res.code);
if (res.code === 0) {
queryClient.invalidateQueries({ queryKey: ['data-source'] });

View File

@@ -79,7 +79,7 @@ const DataSource = () => {
handleAddOk,
hideAddingModal,
showAddingModal,
} = useAddDataSource();
} = useAddDataSource({});
return (
<ProfileSettingWrapperCard

View File

@@ -19,13 +19,17 @@ const dataSourceService = registerServer<keyof typeof methods>(
);
export const deleteDataSource = (id: string) =>
request.post(api.dataSourceDel(id));
request.delete(api.dataSourceDel(id));
export const dataSourceResume = (id: string, data: { resume: boolean }) => {
return request.put(api.dataSourceResume(id), { data });
return request.post(api.dataSourceResume(id), { data });
};
export const dataSourceRebuild = (id: string, data: { kb_id: string }) => {
return request.put(api.dataSourceRebuild(id), { data });
return request.post(api.dataSourceRebuild(id), { data });
};
export const dataSourceUpdate = (id: string, data: { kb_id: string }) => {
return request.patch(api.dataSourceUpdate(id), { data });
};
export const getDataSourceLogs = (id: string, params?: any) =>

View File

@@ -35,19 +35,20 @@ export default {
deleteFactory: `${webAPI}/llm/delete_factory`,
// data source
dataSourceSet: `${webAPI}/connector/set`,
dataSourceList: `${webAPI}/connector/list`,
dataSourceDel: (id: string) => `${webAPI}/connector/${id}/rm`,
dataSourceResume: (id: string) => `${webAPI}/connector/${id}/resume`,
dataSourceRebuild: (id: string) => `${webAPI}/connector/${id}/rebuild`,
dataSourceLogs: (id: string) => `${webAPI}/connector/${id}/logs`,
dataSourceDetail: (id: string) => `${webAPI}/connector/${id}`,
dataSourceUpdate: (id:string) => `${restAPIv1}/connectors/${id}`,
dataSourceSet: `${restAPIv1}/connectors`,
dataSourceList: `${restAPIv1}/connectors`,
dataSourceDel: (id: string) => `${restAPIv1}/connectors/${id}`,
dataSourceResume: (id: string) => `${restAPIv1}/connectors/${id}/resume`,
dataSourceRebuild: (id: string) => `${restAPIv1}/connectors/${id}/rebuild`,
dataSourceLogs: (id: string) => `${restAPIv1}/connectors/${id}/logs`,
dataSourceDetail: (id: string) => `${restAPIv1}/connectors/${id}`,
googleWebAuthStart: (type: 'google-drive' | 'gmail') =>
`${webAPI}/connector/google/oauth/web/start?type=${type}`,
`${restAPIv1}/connectors/google/oauth/web/start?type=${type}`,
googleWebAuthResult: (type: 'google-drive' | 'gmail') =>
`${webAPI}/connector/google/oauth/web/result?type=${type}`,
boxWebAuthStart: () => `${webAPI}/connector/box/oauth/web/start`,
boxWebAuthResult: () => `${webAPI}/connector/box/oauth/web/result`,
`${restAPIv1}/connectors/google/oauth/web/result?type=${type}`,
boxWebAuthStart: () => `${restAPIv1}/connectors/box/oauth/web/start`,
boxWebAuthResult: () => `${restAPIv1}/connectors/box/oauth/web/result`,
// plugin
llmTools: `${webAPI}/plugin/llm_tools`,