diff --git a/internal/database/migrations.go b/internal/database/migrations.go index 6f97dd658aa..6906977664a 100644 --- a/internal/database/migrations.go +++ b/internal/database/migrations.go @@ -1373,4 +1373,11 @@ var migrations = [...]func(tx *sql.Tx) error{ _, err = tx.Exec(sql) return err }, + func(tx *sql.Tx) (err error) { + sql := ` + ALTER TABLE integrations ADD COLUMN linkwarden_collection_id int; + ` + _, err = tx.Exec(sql) + return err + }, } diff --git a/internal/integration/integration.go b/internal/integration/integration.go index 76be9e5bcf7..a078a54ed55 100644 --- a/internal/integration/integration.go +++ b/internal/integration/integration.go @@ -275,17 +275,20 @@ func SendEntry(entry *model.Entry, userIntegrations *model.Integration) { slog.Int64("user_id", userIntegrations.UserID), slog.Int64("entry_id", entry.ID), slog.String("entry_url", entry.URL), + slog.Any("collection_id", userIntegrations.LinkwardenCollectionId), ) client := linkwarden.NewClient( userIntegrations.LinkwardenURL, userIntegrations.LinkwardenAPIKey, + userIntegrations.LinkwardenCollectionId, ) if err := client.CreateBookmark(entry.URL, entry.Title); err != nil { slog.Error("Unable to send entry to Linkwarden", slog.Int64("user_id", userIntegrations.UserID), slog.Int64("entry_id", entry.ID), slog.String("entry_url", entry.URL), + slog.Any("collection_id", userIntegrations.LinkwardenCollectionId), slog.Any("error", err), ) } diff --git a/internal/integration/linkwarden/linkwarden.go b/internal/integration/linkwarden/linkwarden.go index 2351f867b40..9bc6b9788f1 100644 --- a/internal/integration/linkwarden/linkwarden.go +++ b/internal/integration/linkwarden/linkwarden.go @@ -8,6 +8,7 @@ import ( "encoding/json" "errors" "fmt" + "io" "net/http" "time" @@ -18,12 +19,23 @@ import ( const defaultClientTimeout = 10 * time.Second type Client struct { - baseURL string - apiKey string + baseURL string + apiKey string + collectionId *int64 } -func NewClient(baseURL, apiKey string) *Client { - return &Client{baseURL: baseURL, apiKey: apiKey} +type linkwardenCollection struct { + Id *int64 `json:"id"` +} + +type linkwardenRequest struct { + URL string `json:"url"` + Name string `json:"name"` + Collection *linkwardenCollection `json:"collection,omitempty"` +} + +func NewClient(baseURL, apiKey string, collectionId *int64) *Client { + return &Client{baseURL: baseURL, apiKey: apiKey, collectionId: collectionId} } func (c *Client) CreateBookmark(entryURL, entryTitle string) error { @@ -36,10 +48,16 @@ func (c *Client) CreateBookmark(entryURL, entryTitle string) error { return fmt.Errorf(`linkwarden: invalid API endpoint: %v`, err) } - requestBody, err := json.Marshal(map[string]string{ - "url": entryURL, - "name": entryTitle, - }) + payload := linkwardenRequest{ + URL: entryURL, + Name: entryTitle, + } + + if c.collectionId != nil { + payload.Collection = &linkwardenCollection{Id: c.collectionId} + } + + requestBody, err := json.Marshal(payload) if err != nil { return fmt.Errorf("linkwarden: unable to encode request body: %v", err) @@ -61,8 +79,13 @@ func (c *Client) CreateBookmark(entryURL, entryTitle string) error { } defer response.Body.Close() + responseBody, err := io.ReadAll(response.Body) + if err != nil { + return fmt.Errorf("linkwarden: unable to read response body: %v", err) + } + if response.StatusCode >= 400 { - return fmt.Errorf("linkwarden: unable to create link: url=%s status=%d", apiEndpoint, response.StatusCode) + return fmt.Errorf("linkwarden: unable to create link: status=%d body=%s", response.StatusCode, string(responseBody)) } return nil diff --git a/internal/integration/linkwarden/linkwarden_test.go b/internal/integration/linkwarden/linkwarden_test.go new file mode 100644 index 00000000000..e01c73aa31d --- /dev/null +++ b/internal/integration/linkwarden/linkwarden_test.go @@ -0,0 +1,341 @@ +// SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +package linkwarden + +import ( + "encoding/json" + "io" + "net/http" + "net/http/httptest" + "testing" +) + +func TestCreateBookmark(t *testing.T) { + tests := []struct { + name string + baseURL string + apiKey string + collectionId *int64 + entryURL string + entryTitle string + serverResponse func(w http.ResponseWriter, r *http.Request, t *testing.T, collectionId *int64) + wantErr bool + errContains string + }{ + { + name: "successful bookmark creation without collection", + baseURL: "", + apiKey: "test-api-key", + collectionId: nil, + entryURL: "https://example.com", + entryTitle: "Test Article", + serverResponse: func(w http.ResponseWriter, r *http.Request, t *testing.T, collectionId *int64) { + // Verify authorization header + auth := r.Header.Get("Authorization") + if auth != "Bearer test-api-key" { + t.Errorf("Expected Authorization header 'Bearer test-api-key', got %s", auth) + } + + // Verify content type + contentType := r.Header.Get("Content-Type") + if contentType != "application/json" { + t.Errorf("Expected Content-Type 'application/json', got %s", contentType) + } + + // Parse and verify request + body, _ := io.ReadAll(r.Body) + var req map[string]interface{} + if err := json.Unmarshal(body, &req); err != nil { + t.Errorf("Failed to parse request body: %v", err) + } + + // Verify URL + if reqURL := req["url"]; reqURL != "https://example.com" { + t.Errorf("Expected URL 'https://example.com', got %v", reqURL) + } + + // Verify title/name + if reqName := req["name"]; reqName != "Test Article" { + t.Errorf("Expected name 'Test Article', got %v", reqName) + } + + // Verify collection is not present when nil + if _, ok := req["collection"]; ok { + t.Error("Expected collection field to be omitted when collectionId is nil") + } + + // Return success response + w.WriteHeader(http.StatusOK) + json.NewEncoder(w).Encode(map[string]interface{}{ + "id": "123", + "url": "https://example.com", + "name": "Test Article", + }) + }, + wantErr: false, + }, + { + name: "successful bookmark creation with collection", + baseURL: "", + apiKey: "test-api-key", + collectionId: int64Ptr(42), + entryURL: "https://example.com/article", + entryTitle: "Test Article With Collection", + serverResponse: func(w http.ResponseWriter, r *http.Request, t *testing.T, collectionId *int64) { + // Verify authorization header + auth := r.Header.Get("Authorization") + if auth != "Bearer test-api-key" { + t.Errorf("Expected Authorization header 'Bearer test-api-key', got %s", auth) + } + + // Parse and verify request + body, _ := io.ReadAll(r.Body) + var req map[string]interface{} + if err := json.Unmarshal(body, &req); err != nil { + t.Errorf("Failed to parse request body: %v", err) + } + + // Verify URL + if reqURL := req["url"]; reqURL != "https://example.com/article" { + t.Errorf("Expected URL 'https://example.com/article', got %v", reqURL) + } + + // Verify title/name + if reqName := req["name"]; reqName != "Test Article With Collection" { + t.Errorf("Expected name 'Test Article With Collection', got %v", reqName) + } + + // Verify collection is present and correct + if collection, ok := req["collection"]; ok { + collectionMap, ok := collection.(map[string]interface{}) + if !ok { + t.Error("Expected collection to be a map") + } + if collectionID, ok := collectionMap["id"]; ok { + // JSON numbers are float64 + if collectionIDFloat, ok := collectionID.(float64); !ok || int64(collectionIDFloat) != 42 { + t.Errorf("Expected collection id 42, got %v", collectionID) + } + } else { + t.Error("Expected collection to have 'id' field") + } + } else { + t.Error("Expected collection field to be present when collectionId is set") + } + + // Return success response + w.WriteHeader(http.StatusOK) + json.NewEncoder(w).Encode(map[string]interface{}{ + "id": "124", + "url": "https://example.com/article", + "name": "Test Article With Collection", + }) + }, + wantErr: false, + }, + { + name: "missing API key", + baseURL: "", + apiKey: "", + collectionId: nil, + entryURL: "https://example.com", + entryTitle: "Test", + serverResponse: func(w http.ResponseWriter, r *http.Request, t *testing.T, collectionId *int64) { + // Should not be called + t.Error("Server should not be called when API key is missing") + }, + wantErr: true, + errContains: "missing base URL or API key", + }, + { + name: "server error", + baseURL: "", + apiKey: "test-api-key", + collectionId: nil, + entryURL: "https://example.com", + entryTitle: "Test", + serverResponse: func(w http.ResponseWriter, r *http.Request, t *testing.T, collectionId *int64) { + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte(`{"error": "Internal server error"}`)) + }, + wantErr: true, + errContains: "unable to create link: status=500", + }, + { + name: "bad request with null collection id error", + baseURL: "", + apiKey: "test-api-key", + collectionId: nil, + entryURL: "https://example.com", + entryTitle: "Test", + serverResponse: func(w http.ResponseWriter, r *http.Request, t *testing.T, collectionId *int64) { + w.WriteHeader(http.StatusBadRequest) + w.Write([]byte(`{"response":"Error: Expected number, received null [collection, id]"}`)) + }, + wantErr: true, + errContains: "unable to create link: status=400", + }, + { + name: "unauthorized", + baseURL: "", + apiKey: "invalid-key", + collectionId: nil, + entryURL: "https://example.com", + entryTitle: "Test", + serverResponse: func(w http.ResponseWriter, r *http.Request, t *testing.T, collectionId *int64) { + w.WriteHeader(http.StatusUnauthorized) + w.Write([]byte(`{"error": "Unauthorized"}`)) + }, + wantErr: true, + errContains: "unable to create link: status=401", + }, + { + name: "invalid base URL", + baseURL: ":", + apiKey: "test-api-key", + collectionId: nil, + entryURL: "https://example.com", + entryTitle: "Test", + serverResponse: func(w http.ResponseWriter, r *http.Request, t *testing.T, collectionId *int64) { + // Should not be called + t.Error("Server should not be called when base URL is invalid") + }, + wantErr: true, + errContains: "invalid API endpoint", + }, + { + name: "missing base URL", + baseURL: "", + apiKey: "", + collectionId: nil, + entryURL: "https://example.com", + entryTitle: "Test", + serverResponse: func(w http.ResponseWriter, r *http.Request, t *testing.T, collectionId *int64) { + // Should not be called + t.Error("Server should not be called when base URL is missing") + }, + wantErr: true, + errContains: "missing base URL or API key", + }, + { + name: "network connection error", + baseURL: "http://localhost:1", // Invalid port that should fail to connect + apiKey: "test-api-key", + collectionId: nil, + entryURL: "https://example.com", + entryTitle: "Test", + serverResponse: func(w http.ResponseWriter, r *http.Request, t *testing.T, collectionId *int64) { + // Should not be called due to connection failure + t.Error("Server should not be called when connection fails") + }, + wantErr: true, + errContains: "unable to send request", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Create test server only if we have a valid apiKey and don't have a custom baseURL for error testing + var server *httptest.Server + if tt.apiKey != "" && tt.baseURL != ":" && tt.baseURL != "http://localhost:1" { + server = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + tt.serverResponse(w, r, t, tt.collectionId) + })) + defer server.Close() + } + + // Use test server URL if baseURL is empty and we have a server + baseURL := tt.baseURL + if baseURL == "" && server != nil { + baseURL = server.URL + } + + // Create client + client := NewClient(baseURL, tt.apiKey, tt.collectionId) + + // Call CreateBookmark + err := client.CreateBookmark(tt.entryURL, tt.entryTitle) + + // Check error + if tt.wantErr { + if err == nil { + t.Error("Expected error, got nil") + } else if tt.errContains != "" && !contains(err.Error(), tt.errContains) { + t.Errorf("Expected error to contain '%s', got '%s'", tt.errContains, err.Error()) + } + } else { + if err != nil { + t.Errorf("Expected no error, got %v", err) + } + } + }) + } +} + +func TestNewClient(t *testing.T) { + tests := []struct { + name string + baseURL string + apiKey string + collectionId *int64 + }{ + { + name: "client without collection", + baseURL: "https://linkwarden.example.com", + apiKey: "test-key", + collectionId: nil, + }, + { + name: "client with collection", + baseURL: "https://linkwarden.example.com", + apiKey: "test-key", + collectionId: int64Ptr(123), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + client := NewClient(tt.baseURL, tt.apiKey, tt.collectionId) + + if client.baseURL != tt.baseURL { + t.Errorf("Expected baseURL %s, got %s", tt.baseURL, client.baseURL) + } + + if client.apiKey != tt.apiKey { + t.Errorf("Expected apiKey %s, got %s", tt.apiKey, client.apiKey) + } + + if tt.collectionId == nil { + if client.collectionId != nil { + t.Errorf("Expected collectionId to be nil, got %v", *client.collectionId) + } + } else { + if client.collectionId == nil { + t.Error("Expected collectionId to be set, got nil") + } else if *client.collectionId != *tt.collectionId { + t.Errorf("Expected collectionId %d, got %d", *tt.collectionId, *client.collectionId) + } + } + }) + } +} + +// Helper function to create int64 pointer +func int64Ptr(i int64) *int64 { + return &i +} + +// Helper function to check if string contains substring +func contains(s, substr string) bool { + return len(s) >= len(substr) && (s == substr || len(s) > len(substr) && containsSubstring(s, substr)) +} + +func containsSubstring(s, substr string) bool { + for i := 0; i <= len(s)-len(substr); i++ { + if s[i:i+len(substr)] == substr { + return true + } + } + return false +} diff --git a/internal/locale/translations/de_DE.json b/internal/locale/translations/de_DE.json index aaf8c48ac07..a3d1b04bdad 100644 --- a/internal/locale/translations/de_DE.json +++ b/internal/locale/translations/de_DE.json @@ -266,6 +266,7 @@ "form.integration.linkwarden_activate": "Artikel in Linkwarden speichern", "form.integration.linkwarden_api_key": "Linkwarden-API-Schlüssel", "form.integration.linkwarden_endpoint": "Linkwarden-Base-URL", + "form.integration.linkwarden_collection_id": "Linkwarden Collection ID", "form.integration.matrix_bot_activate": "Neue Artikel in Matrix übertragen", "form.integration.matrix_bot_chat_id": "ID des Matrix-Raums", "form.integration.matrix_bot_password": "Passwort für Matrix-Benutzer", @@ -629,4 +630,4 @@ "time_elapsed.yesterday": "gestern", "tooltip.keyboard_shortcuts": "Tastenkürzel: %s", "tooltip.logged_user": "Angemeldet als %s" -} +} \ No newline at end of file diff --git a/internal/locale/translations/el_EL.json b/internal/locale/translations/el_EL.json index dc4d7da7b4e..4739c786b25 100644 --- a/internal/locale/translations/el_EL.json +++ b/internal/locale/translations/el_EL.json @@ -266,6 +266,7 @@ "form.integration.linkwarden_activate": "Αποθήκευση άρθρων στο Linkwarden", "form.integration.linkwarden_api_key": "Κλειδί API Linkwarden", "form.integration.linkwarden_endpoint": "URL βάσης Linkwarden", + "form.integration.linkwarden_collection_id": "Linkwarden Collection ID", "form.integration.matrix_bot_activate": "Μεταφορά νέων άρθρων στο Matrix", "form.integration.matrix_bot_chat_id": "Αναγνωριστικό της αίθουσας Matrix", "form.integration.matrix_bot_password": "Κωδικός πρόσβασης για τον χρήστη Matrix", diff --git a/internal/locale/translations/en_US.json b/internal/locale/translations/en_US.json index 192fee0dae4..eacaf03f00e 100644 --- a/internal/locale/translations/en_US.json +++ b/internal/locale/translations/en_US.json @@ -266,6 +266,7 @@ "form.integration.linkwarden_activate": "Save entries to Linkwarden", "form.integration.linkwarden_api_key": "Linkwarden API key", "form.integration.linkwarden_endpoint": "Linkwarden Base URL", + "form.integration.linkwarden_collection_id": "Linkwarden Collection ID", "form.integration.matrix_bot_activate": "Push new entries to Matrix", "form.integration.matrix_bot_chat_id": "ID of Matrix Room", "form.integration.matrix_bot_password": "Password for Matrix user", @@ -629,4 +630,4 @@ "time_elapsed.yesterday": "yesterday", "tooltip.keyboard_shortcuts": "Keyboard Shortcut: %s", "tooltip.logged_user": "Logged in as %s" -} +} \ No newline at end of file diff --git a/internal/locale/translations/es_ES.json b/internal/locale/translations/es_ES.json index 720edfe738c..eb561eb13f7 100644 --- a/internal/locale/translations/es_ES.json +++ b/internal/locale/translations/es_ES.json @@ -266,6 +266,7 @@ "form.integration.linkwarden_activate": "Enviar artículos a Linkwarden", "form.integration.linkwarden_api_key": "Clave de API de Linkwarden", "form.integration.linkwarden_endpoint": "URL base de Linkwarden", + "form.integration.linkwarden_collection_id": "Linkwarden Collection ID", "form.integration.matrix_bot_activate": "Transferir nuevos artículos a Matrix", "form.integration.matrix_bot_chat_id": "ID de la sala de Matrix", "form.integration.matrix_bot_password": "Contraseña para el usuario de Matrix", diff --git a/internal/locale/translations/fi_FI.json b/internal/locale/translations/fi_FI.json index 2cefea7acb6..d3c4fe3bfd0 100644 --- a/internal/locale/translations/fi_FI.json +++ b/internal/locale/translations/fi_FI.json @@ -266,6 +266,7 @@ "form.integration.linkwarden_activate": "Tallenna artikkelit Linkkiin", "form.integration.linkwarden_api_key": "Linkwarden API-avain", "form.integration.linkwarden_endpoint": "Linkwarden Base URL", + "form.integration.linkwarden_collection_id": "Linkwarden Collection ID", "form.integration.matrix_bot_activate": "Siirrä uudet artikkelit Matrixiin", "form.integration.matrix_bot_chat_id": "Matrix-huoneen tunnus", "form.integration.matrix_bot_password": "Matrix-käyttäjän salasana", diff --git a/internal/locale/translations/fr_FR.json b/internal/locale/translations/fr_FR.json index 06c77beb201..f0fa2bb3772 100644 --- a/internal/locale/translations/fr_FR.json +++ b/internal/locale/translations/fr_FR.json @@ -266,6 +266,7 @@ "form.integration.linkwarden_activate": "Sauvegarder les articles vers Linkwarden", "form.integration.linkwarden_api_key": "Clé d'API de Linkwarden", "form.integration.linkwarden_endpoint": "URL de base de Linkwarden", + "form.integration.linkwarden_collection_id": "Linkwarden Collection ID", "form.integration.matrix_bot_activate": "Envoyer les nouveaux articles vers Matrix", "form.integration.matrix_bot_chat_id": "Identifiant de la salle Matrix", "form.integration.matrix_bot_password": "Mot de passe de l'utilisateur Matrix", diff --git a/internal/locale/translations/hi_IN.json b/internal/locale/translations/hi_IN.json index 96143ef4c51..7e46405c703 100644 --- a/internal/locale/translations/hi_IN.json +++ b/internal/locale/translations/hi_IN.json @@ -266,6 +266,7 @@ "form.integration.linkwarden_activate": "Save entries to Linkwarden", "form.integration.linkwarden_api_key": "Linkwarden API key", "form.integration.linkwarden_endpoint": "लिंकवर्डन बेस यूआरएलL", + "form.integration.linkwarden_collection_id": "Linkwarden Collection ID", "form.integration.matrix_bot_activate": "नए लेखों को मैट्रिक्स में स्थानांतरित करें", "form.integration.matrix_bot_chat_id": "मैट्रिक्स रूम की आईडी", "form.integration.matrix_bot_password": "मैट्रिक्स उपयोगकर्ता के लिए पासवर्ड", diff --git a/internal/locale/translations/id_ID.json b/internal/locale/translations/id_ID.json index b4af4bbc8eb..0895dec55da 100644 --- a/internal/locale/translations/id_ID.json +++ b/internal/locale/translations/id_ID.json @@ -263,6 +263,7 @@ "form.integration.linkwarden_activate": "Simpan artikel ke Linkwarden", "form.integration.linkwarden_api_key": "Kunci API Linkwarden", "form.integration.linkwarden_endpoint": "URL Dasar Linkwarden", + "form.integration.linkwarden_collection_id": "Linkwarden Collection ID", "form.integration.matrix_bot_activate": "Kirim entri baru ke Matrix", "form.integration.matrix_bot_chat_id": "ID Ruang Matrix", "form.integration.matrix_bot_password": "Kata Sandi Matrix", diff --git a/internal/locale/translations/it_IT.json b/internal/locale/translations/it_IT.json index 5ba305fc37f..2aafd95e036 100644 --- a/internal/locale/translations/it_IT.json +++ b/internal/locale/translations/it_IT.json @@ -266,6 +266,7 @@ "form.integration.linkwarden_activate": "Salva gli articoli su Linkwarden", "form.integration.linkwarden_api_key": "API key dell'account Linkwarden", "form.integration.linkwarden_endpoint": "URL di base di Linkwarden", + "form.integration.linkwarden_collection_id": "Linkwarden Collection ID", "form.integration.matrix_bot_activate": "Trasferimento di nuovi articoli a Matrix", "form.integration.matrix_bot_chat_id": "ID della stanza Matrix", "form.integration.matrix_bot_password": "Password per l'utente Matrix", diff --git a/internal/locale/translations/ja_JP.json b/internal/locale/translations/ja_JP.json index cb951369df1..e11ad665f6a 100644 --- a/internal/locale/translations/ja_JP.json +++ b/internal/locale/translations/ja_JP.json @@ -263,6 +263,7 @@ "form.integration.linkwarden_activate": "Linkwarden に記事を保存する", "form.integration.linkwarden_api_key": "Linkwarden の API key", "form.integration.linkwarden_endpoint": "リンクワーデン ベース URL", + "form.integration.linkwarden_collection_id": "Linkwarden Collection ID", "form.integration.matrix_bot_activate": "新しい記事をMatrixに転送する", "form.integration.matrix_bot_chat_id": "MatrixルームのID", "form.integration.matrix_bot_password": "Matrixユーザ用パスワード", diff --git a/internal/locale/translations/nan_Latn_pehoeji.json b/internal/locale/translations/nan_Latn_pehoeji.json index 473b04fac4e..a72f7e898c4 100644 --- a/internal/locale/translations/nan_Latn_pehoeji.json +++ b/internal/locale/translations/nan_Latn_pehoeji.json @@ -263,6 +263,7 @@ "form.integration.linkwarden_activate": "Pó-chûn siau-sit kàu Linkwarden", "form.integration.linkwarden_api_key": "Linkwarden API só-sî", "form.integration.linkwarden_endpoint": "Linkwarden Base URL", + "form.integration.linkwarden_collection_id": "Linkwarden Collection ID", "form.integration.matrix_bot_activate": "Thui-sàng siau-sit kàu Matrix", "form.integration.matrix_bot_chat_id": "Matrix pâng-keng ID", "form.integration.matrix_bot_password": "Matrix bi̍t-bé", @@ -611,4 +612,4 @@ "time_elapsed.yesterday": "cha-hng", "tooltip.keyboard_shortcuts": "Khoài-sok khí:%s", "tooltip.logged_user": "Chit-má teng-lo̍k--ê: %s" -} +} \ No newline at end of file diff --git a/internal/locale/translations/nl_NL.json b/internal/locale/translations/nl_NL.json index 5a12151dc1c..ff5c6ab2d83 100644 --- a/internal/locale/translations/nl_NL.json +++ b/internal/locale/translations/nl_NL.json @@ -266,6 +266,7 @@ "form.integration.linkwarden_activate": "Artikelen opslaan in Linkwarden", "form.integration.linkwarden_api_key": "Linkwarden API-sleutel", "form.integration.linkwarden_endpoint": "Linkwarden Basis URL", + "form.integration.linkwarden_collection_id": "Linkwarden Collection ID", "form.integration.matrix_bot_activate": "Nieuwe artikelen opslaan in Matrix", "form.integration.matrix_bot_chat_id": "ID van Matrix-kamer", "form.integration.matrix_bot_password": "Wachtwoord voor Matrix-gebruiker", diff --git a/internal/locale/translations/pl_PL.json b/internal/locale/translations/pl_PL.json index b0d90999225..c687c5b913e 100644 --- a/internal/locale/translations/pl_PL.json +++ b/internal/locale/translations/pl_PL.json @@ -269,6 +269,7 @@ "form.integration.linkwarden_activate": "Zapisuj wpisy w Linkwarden", "form.integration.linkwarden_api_key": "Klucz API do Linkwarden", "form.integration.linkwarden_endpoint": "Podstawowy adres URL Linkwarden", + "form.integration.linkwarden_collection_id": "Linkwarden Collection ID", "form.integration.matrix_bot_activate": "Przesyłaj nowe wpisy do Matrix", "form.integration.matrix_bot_chat_id": "Identyfikator pokoju Matrix", "form.integration.matrix_bot_password": "Hasło do Matrix", diff --git a/internal/locale/translations/pt_BR.json b/internal/locale/translations/pt_BR.json index a77aed6b6e3..03bcd7462af 100644 --- a/internal/locale/translations/pt_BR.json +++ b/internal/locale/translations/pt_BR.json @@ -266,6 +266,7 @@ "form.integration.linkwarden_activate": "Salvar itens no Linkwarden", "form.integration.linkwarden_api_key": "Chave de API do Linkwarden", "form.integration.linkwarden_endpoint": "URL base do Linkwarden", + "form.integration.linkwarden_collection_id": "Linkwarden Collection ID", "form.integration.matrix_bot_activate": "Transferir novos artigos para o Matrix", "form.integration.matrix_bot_chat_id": "Identificação da sala Matrix", "form.integration.matrix_bot_password": "Palavra-passe para utilizador da Matrix", diff --git a/internal/locale/translations/ro_RO.json b/internal/locale/translations/ro_RO.json index fbd9bcf62e1..76dce4eca8c 100644 --- a/internal/locale/translations/ro_RO.json +++ b/internal/locale/translations/ro_RO.json @@ -269,6 +269,7 @@ "form.integration.linkwarden_activate": "Salvează intrările în Linkwarden", "form.integration.linkwarden_api_key": "Cheie API Linkwarden", "form.integration.linkwarden_endpoint": "URL-ul de bază Linkwarden", + "form.integration.linkwarden_collection_id": "Linkwarden Collection ID", "form.integration.matrix_bot_activate": "Împinge intrările noi pe Matrix", "form.integration.matrix_bot_chat_id": "ID-ul Camerei Matrix", "form.integration.matrix_bot_password": "Parola utilizatorului Matrix", diff --git a/internal/locale/translations/ru_RU.json b/internal/locale/translations/ru_RU.json index c9a70c5605d..46faf202c61 100644 --- a/internal/locale/translations/ru_RU.json +++ b/internal/locale/translations/ru_RU.json @@ -269,6 +269,7 @@ "form.integration.linkwarden_activate": "Сохранять статьи в Linkwarden", "form.integration.linkwarden_api_key": "API-ключ Linkwarden", "form.integration.linkwarden_endpoint": "Базовый URL-адрес Linkwarden", + "form.integration.linkwarden_collection_id": "Linkwarden Collection ID", "form.integration.matrix_bot_activate": "Отправлять статьи в Matrix", "form.integration.matrix_bot_chat_id": "ID комнаты Matrix", "form.integration.matrix_bot_password": "Пароль пользователя Matrix", diff --git a/internal/locale/translations/tr_TR.json b/internal/locale/translations/tr_TR.json index 6838d53c6e9..06a9e1d1137 100644 --- a/internal/locale/translations/tr_TR.json +++ b/internal/locale/translations/tr_TR.json @@ -266,6 +266,7 @@ "form.integration.linkwarden_activate": "Makaleleri Linkwarden'e kaydet", "form.integration.linkwarden_api_key": "Linkwarden API Anahtarı", "form.integration.linkwarden_endpoint": "Linkwarden Temel URL'si", + "form.integration.linkwarden_collection_id": "Linkwarden Collection ID", "form.integration.matrix_bot_activate": "Yeni makaleleri Matrix'e aktarın", "form.integration.matrix_bot_chat_id": "Matrix odasının kimliği", "form.integration.matrix_bot_password": "Matrix kullanıcısı için parola", diff --git a/internal/locale/translations/uk_UA.json b/internal/locale/translations/uk_UA.json index e795676de49..854b7e7251e 100644 --- a/internal/locale/translations/uk_UA.json +++ b/internal/locale/translations/uk_UA.json @@ -269,6 +269,7 @@ "form.integration.linkwarden_activate": "Зберігати статті до Linkwarden", "form.integration.linkwarden_api_key": "Ключ API Linkwarden", "form.integration.linkwarden_endpoint": "Базова URL-адреса Linkwarden", + "form.integration.linkwarden_collection_id": "Linkwarden Collection ID", "form.integration.matrix_bot_activate": "Перенесення нових статей в Матрицю", "form.integration.matrix_bot_chat_id": "Ідентифікатор кімнати Матриці", "form.integration.matrix_bot_password": "Пароль для користувача Matrix", diff --git a/internal/locale/translations/zh_CN.json b/internal/locale/translations/zh_CN.json index ad7761fbdf7..c16cf72a04a 100644 --- a/internal/locale/translations/zh_CN.json +++ b/internal/locale/translations/zh_CN.json @@ -263,6 +263,7 @@ "form.integration.linkwarden_activate": "保存条目到 Linkwarden", "form.integration.linkwarden_api_key": "Linkwarden API 密钥", "form.integration.linkwarden_endpoint": "Linkwarden 基本 URL", + "form.integration.linkwarden_collection_id": "Linkwarden Collection ID", "form.integration.matrix_bot_activate": "推送新条目到 Matrix", "form.integration.matrix_bot_chat_id": "Matrix 房间 ID", "form.integration.matrix_bot_password": "Matrix 用户密码", @@ -611,4 +612,4 @@ "time_elapsed.yesterday": "昨天", "tooltip.keyboard_shortcuts": "键盘快捷键:%s", "tooltip.logged_user": "登录用户:%s" -} +} \ No newline at end of file diff --git a/internal/locale/translations/zh_TW.json b/internal/locale/translations/zh_TW.json index e9e42d69a00..3661ecdd794 100644 --- a/internal/locale/translations/zh_TW.json +++ b/internal/locale/translations/zh_TW.json @@ -263,6 +263,7 @@ "form.integration.linkwarden_activate": "儲存文章到 Linkwarden", "form.integration.linkwarden_api_key": "Linkwarden API 金鑰", "form.integration.linkwarden_endpoint": "Linkwarden 基本 URL", + "form.integration.linkwarden_collection_id": "Linkwarden Collection ID", "form.integration.matrix_bot_activate": "推送文章到 Matrix", "form.integration.matrix_bot_chat_id": "Matrix 房間 ID", "form.integration.matrix_bot_password": "Matrix 密碼", diff --git a/internal/model/integration.go b/internal/model/integration.go index 47676678eb3..f60c9e2658d 100644 --- a/internal/model/integration.go +++ b/internal/model/integration.go @@ -68,6 +68,7 @@ type Integration struct { LinkwardenEnabled bool LinkwardenURL string LinkwardenAPIKey string + LinkwardenCollectionId *int64 MatrixBotEnabled bool MatrixBotUser string MatrixBotPassword string diff --git a/internal/storage/integration.go b/internal/storage/integration.go index f5f94a2684e..b441fb536ba 100644 --- a/internal/storage/integration.go +++ b/internal/storage/integration.go @@ -164,6 +164,7 @@ func (s *Storage) Integration(userID int64) (*model.Integration, error) { linkwarden_enabled, linkwarden_url, linkwarden_api_key, + linkwarden_collection_id, matrix_bot_enabled, matrix_bot_user, matrix_bot_password, @@ -291,6 +292,7 @@ func (s *Storage) Integration(userID int64) (*model.Integration, error) { &integration.LinkwardenEnabled, &integration.LinkwardenURL, &integration.LinkwardenAPIKey, + &integration.LinkwardenCollectionId, &integration.MatrixBotEnabled, &integration.MatrixBotUser, &integration.MatrixBotPassword, @@ -491,9 +493,10 @@ func (s *Storage) UpdateIntegration(integration *model.Integration) error { linktaco_org_slug=$116, linktaco_tags=$117, linktaco_visibility=$118, - archiveorg_enabled=$119 + archiveorg_enabled=$119, + linkwarden_collection_id=$120 WHERE - user_id=$120 + user_id=$121 ` _, err := s.db.Exec( query, @@ -616,6 +619,7 @@ func (s *Storage) UpdateIntegration(integration *model.Integration) error { integration.LinktacoTags, integration.LinktacoVisibility, integration.ArchiveorgEnabled, + integration.LinkwardenCollectionId, integration.UserID, ) diff --git a/internal/template/templates/views/integrations.html b/internal/template/templates/views/integrations.html index 9828b20196b..a26660c81ff 100644 --- a/internal/template/templates/views/integrations.html +++ b/internal/template/templates/views/integrations.html @@ -284,6 +284,9 @@