Repository: miniflux/v2 (language: go)

Issue:
[FR] Provide an API to know if the third-party services integration settings are configured
- [x] I have read this document: https://miniflux.app/opinionated.html#feature-request

I have reviewed the documentation multiple times but couldn't find an API that allows checking whether the third-party services integration settings are configured.
If such an API already exists, could you please point me to the relevant documentation?
If not, I would like to request the implementation of an API endpoint that provides the ability to determine if third-party services integration settings are configured or not.

This feature would be particularly helpful in scenarios where we need to decide whether or not to display `Save` option in the UI, based on the configuration status of third-party services.


Discussion / hints:
If I'm not mistaken, this is the relevant logic:

https://github.com/miniflux/v2/blob/faa70f3019eed19dec04b26c82199983d3324cbd/internal/storage/integration.go#L545-L582

Fix patch:
diff --git a/.github/workflows/linters.yml b/.github/workflows/linters.yml
index d5ecac7fa2d..695dea78b15 100644
--- a/.github/workflows/linters.yml
+++ b/.github/workflows/linters.yml
@@ -32,7 +32,11 @@ jobs:
       - run: "go vet ./..."
       - uses: golangci/golangci-lint-action@v6
         with:
-          args: --timeout 10m --skip-dirs tests --disable errcheck --enable sqlclosecheck --enable misspell --enable gofmt --enable goimports --enable whitespace --enable gocritic
+          args: >
+            --timeout 10m
+            --exclude-dirs=tests
+            --disable errcheck
+            --enable sqlclosecheck,misspell,gofmt,goimports,whitespace,gocritic
       - uses: dominikh/staticcheck-action@v1.3.1
         with:
           version: "2024.1.1"
diff --git a/client/client.go b/client/client.go
index 11659164fba..a463b2d725a 100644
--- a/client/client.go
+++ b/client/client.go
@@ -185,6 +185,25 @@ func (c *Client) MarkAllAsRead(userID int64) error {
 	return err
 }
 
+// FetchIntegrationsStatus fetches the integrations status for a user.
+func (c *Client) FetchIntegrationsStatus() (bool, error) {
+	body, err := c.request.Get("/v1/users/integrations/status")
+	if err != nil {
+		return false, err
+	}
+	defer body.Close()
+
+	var response struct {
+		HasIntegrations bool `json:"has_integrations"`
+	}
+
+	if err := json.NewDecoder(body).Decode(&response); err != nil {
+		return false, fmt.Errorf("miniflux: response error (%v)", err)
+	}
+
+	return response.HasIntegrations, nil
+}
+
 // Discover try to find subscriptions from a website.
 func (c *Client) Discover(url string) (Subscriptions, error) {
 	body, err := c.request.Post("/v1/discover", map[string]string{"url": url})
diff --git a/internal/api/api.go b/internal/api/api.go
index 48d13039cf6..19d5ba62d0c 100644
--- a/internal/api/api.go
+++ b/internal/api/api.go
@@ -37,6 +37,7 @@ func Serve(router *mux.Router, store *storage.Storage, pool *worker.Pool) {
 	sr.HandleFunc("/users/{userID:[0-9]+}", handler.updateUser).Methods(http.MethodPut)
 	sr.HandleFunc("/users/{userID:[0-9]+}", handler.removeUser).Methods(http.MethodDelete)
 	sr.HandleFunc("/users/{userID:[0-9]+}/mark-all-as-read", handler.markUserAsRead).Methods(http.MethodPut)
+	sr.HandleFunc("/users/integrations/status", handler.getIntegrationsStatus).Methods(http.MethodGet)
 	sr.HandleFunc("/users/{username}", handler.userByUsername).Methods(http.MethodGet)
 	sr.HandleFunc("/me", handler.currentUser).Methods(http.MethodGet)
 	sr.HandleFunc("/categories", handler.createCategory).Methods(http.MethodPost)
diff --git a/internal/api/user.go b/internal/api/user.go
index 71d70e9c5b5..112c19a25d4 100644
--- a/internal/api/user.go
+++ b/internal/api/user.go
@@ -130,6 +130,25 @@ func (h *handler) markUserAsRead(w http.ResponseWriter, r *http.Request) {
 	json.NoContent(w, r)
 }
 
+func (h *handler) getIntegrationsStatus(w http.ResponseWriter, r *http.Request) {
+	userID := request.UserID(r)
+
+	if _, err := h.store.UserByID(userID); err != nil {
+		json.NotFound(w, r)
+		return
+	}
+
+	hasIntegrations := h.store.HasSaveEntry(userID)
+
+	response := struct {
+		HasIntegrations bool `json:"has_integrations"`
+	}{
+		HasIntegrations: hasIntegrations,
+	}
+
+	json.OK(w, r, response)
+}
+
 func (h *handler) users(w http.ResponseWriter, r *http.Request) {
 	if !request.IsAdminUser(r) {
 		json.Forbidden(w, r)


Test patch:
diff --git a/internal/api/api_integration_test.go b/internal/api/api_integration_test.go
index fe172ce587d..8bbb777a495 100644
--- a/internal/api/api_integration_test.go
+++ b/internal/api/api_integration_test.go
@@ -2483,6 +2483,32 @@ func TestSaveEntryEndpoint(t *testing.T) {
 	}
 }
 
+func TestFetchIntegrationsStatusEndpoint(t *testing.T) {
+	testConfig := newIntegrationTestConfig()
+	if !testConfig.isConfigured() {
+		t.Skip(skipIntegrationTestsMessage)
+	}
+
+	adminClient := miniflux.NewClient(testConfig.testBaseURL, testConfig.testAdminUsername, testConfig.testAdminPassword)
+
+	regularTestUser, err := adminClient.CreateUser(testConfig.genRandomUsername(), testConfig.testRegularPassword, false)
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer adminClient.DeleteUser(regularTestUser.ID)
+
+	regularUserClient := miniflux.NewClient(testConfig.testBaseURL, regularTestUser.Username, testConfig.testRegularPassword)
+
+	hasIntegrations, err := regularUserClient.FetchIntegrationsStatus()
+	if err != nil {
+		t.Fatalf("Failed to fetch integrations status: %v", err)
+	}
+
+	if hasIntegrations {
+		t.Fatalf("New user should not have integrations configured")
+	}
+}
+
 func TestFetchContentEndpoint(t *testing.T) {
 	testConfig := newIntegrationTestConfig()
 	if !testConfig.isConfigured() {