From 0cd4257ebcae5e38d66d2d2a3cddcb61675d1041 Mon Sep 17 00:00:00 2001 From: Benjamin Masters Date: Mon, 14 Nov 2022 14:37:06 -0500 Subject: [PATCH] [GH-3410] Healthcheck Endpoint with Server Metadata (#4151) * add ping endpoint and tests * remove unnecessary newlines * fix: invalid Swagger YAML comment blocks * refactor and add 'suite' SKU * generate swagger docs Co-authored-by: Mattermod Co-authored-by: Paul Esch-Laurent --- server/api/cards.go | 12 +- server/api/system.go | 31 + server/api/system_test.go | 143 + server/api/users.go | 2 +- server/app/server_metadata.go | 42 + server/app/server_metadata_test.go | 37 + server/services/store/mockstore/mockstore.go | 14 + .../services/store/sqlstore/public_methods.go | 5 + server/services/store/sqlstore/sqlstore.go | 23 + server/services/store/store.go | 1 + .../docs/html/.openapi-generator/VERSION | 2 +- server/swagger/docs/html/index.html | 4080 ++++++++++++----- server/swagger/swagger.yml | 162 +- 13 files changed, 3373 insertions(+), 1181 deletions(-) create mode 100644 server/api/system_test.go create mode 100644 server/app/server_metadata.go create mode 100644 server/app/server_metadata_test.go diff --git a/server/api/cards.go b/server/api/cards.go index 327aa68b9..4a98c6bfd 100644 --- a/server/api/cards.go +++ b/server/api/cards.go @@ -52,7 +52,7 @@ func (a *API) handleCreateCard(w http.ResponseWriter, r *http.Request) { // description: Disables notifications (for bulk data inserting) // required: false // type: bool - // security: + // security: // - BearerAuth: [] // responses: // '200': @@ -128,7 +128,7 @@ func (a *API) handleCreateCard(w http.ResponseWriter, r *http.Request) { } func (a *API) handleGetCards(w http.ResponseWriter, r *http.Request) { - // swagger:operation GET /boards/{boardID}/cards + // swagger:operation GET /boards/{boardID}/cards getCards // // Fetches cards for the specified board. // @@ -151,7 +151,7 @@ func (a *API) handleGetCards(w http.ResponseWriter, r *http.Request) { // description: Number of cards to return per page(default=100) // required: false // type: integer - // security: + // security: // - BearerAuth: [] // responses: // '200': @@ -252,7 +252,7 @@ func (a *API) handlePatchCard(w http.ResponseWriter, r *http.Request) { // description: Disables notifications (for bulk data patching) // required: false // type: bool - // security: + // security: // - BearerAuth: [] // responses: // '200': @@ -325,7 +325,7 @@ func (a *API) handlePatchCard(w http.ResponseWriter, r *http.Request) { } func (a *API) handleGetCard(w http.ResponseWriter, r *http.Request) { - // swagger:operation GET /cards/{cardID} + // swagger:operation GET /cards/{cardID} getCard // // Fetches the specified card. // @@ -338,7 +338,7 @@ func (a *API) handleGetCard(w http.ResponseWriter, r *http.Request) { // description: Card ID // required: true // type: string - // security: + // security: // - BearerAuth: [] // responses: // '200': diff --git a/server/api/system.go b/server/api/system.go index 83a2cd733..1e579b452 100644 --- a/server/api/system.go +++ b/server/api/system.go @@ -1,6 +1,7 @@ package api import ( + "encoding/json" "net/http" "github.com/gorilla/mux" @@ -9,6 +10,7 @@ import ( func (a *API) registerSystemRoutes(r *mux.Router) { // System APIs r.HandleFunc("/hello", a.handleHello).Methods("GET") + r.HandleFunc("/ping", a.handlePing).Methods("GET") } func (a *API) handleHello(w http.ResponseWriter, r *http.Request) { @@ -24,3 +26,32 @@ func (a *API) handleHello(w http.ResponseWriter, r *http.Request) { // description: success stringResponse(w, "Hello") } + +func (a *API) handlePing(w http.ResponseWriter, r *http.Request) { + // swagger:operation GET /ping ping + // + // Responds with server metadata if the web service is running. + // + // --- + // produces: + // - application/json + // responses: + // '200': + // description: success + serverMetadata := a.app.GetServerMetadata() + + if a.singleUserToken != "" { + serverMetadata.SKU = "personal_desktop" + } + + if serverMetadata.Edition == "plugin" { + serverMetadata.SKU = "suite" + } + + bytes, err := json.Marshal(serverMetadata) + if err != nil { + a.errorResponse(w, r, err) + } + + jsonStringResponse(w, 200, string(bytes)) +} diff --git a/server/api/system_test.go b/server/api/system_test.go new file mode 100644 index 000000000..ce88a1d77 --- /dev/null +++ b/server/api/system_test.go @@ -0,0 +1,143 @@ +package api + +import ( + "encoding/json" + "net/http" + "net/http/httptest" + "runtime" + "testing" + + "github.com/mattermost/focalboard/server/app" + "github.com/mattermost/focalboard/server/model" + "github.com/mattermost/mattermost-server/v6/shared/mlog" +) + +func TestHello(t *testing.T) { + testAPI := API{logger: mlog.CreateConsoleTestLogger(false, mlog.LvlDebug)} + + t.Run("Returns 'Hello' on success", func(t *testing.T) { + request, _ := http.NewRequest(http.MethodGet, "/hello", nil) + response := httptest.NewRecorder() + + testAPI.handleHello(response, request) + + got := response.Body.String() + want := "Hello" + + if got != want { + t.Errorf("got %q want %q", got, want) + } + + if response.Code != http.StatusOK { + t.Errorf("got HTTP %d want %d", response.Code, http.StatusOK) + } + }) +} + +func TestPing(t *testing.T) { + testAPI := API{logger: mlog.CreateConsoleTestLogger(false, mlog.LvlDebug)} + + t.Run("Returns metadata on success", func(t *testing.T) { + request, _ := http.NewRequest(http.MethodGet, "/ping", nil) + response := httptest.NewRecorder() + + testAPI.handlePing(response, request) + + var got app.ServerMetadata + err := json.NewDecoder(response.Body).Decode(&got) + if err != nil { + t.Fatalf("Unable to JSON decode response body %q", response.Body) + } + + want := app.ServerMetadata{ + Version: model.CurrentVersion, + BuildNumber: model.BuildNumber, + BuildDate: model.BuildDate, + Commit: model.BuildHash, + Edition: model.Edition, + DBType: "", + DBVersion: "", + OSType: runtime.GOOS, + OSArch: runtime.GOARCH, + SKU: "personal_server", + } + + if got != want { + t.Errorf("got %q want %q", got, want) + } + + if response.Code != http.StatusOK { + t.Errorf("got HTTP %d want %d", response.Code, http.StatusOK) + } + }) + + t.Run("Sets SKU to 'personal_desktop' when in single-user mode", func(t *testing.T) { + testAPI.singleUserToken = "abc-123-xyz-456" + request, _ := http.NewRequest(http.MethodGet, "/ping", nil) + response := httptest.NewRecorder() + + testAPI.handlePing(response, request) + + var got app.ServerMetadata + err := json.NewDecoder(response.Body).Decode(&got) + if err != nil { + t.Fatalf("Unable to JSON decode response body %q", response.Body) + } + + want := app.ServerMetadata{ + Version: model.CurrentVersion, + BuildNumber: model.BuildNumber, + BuildDate: model.BuildDate, + Commit: model.BuildHash, + Edition: model.Edition, + DBType: "", + DBVersion: "", + OSType: runtime.GOOS, + OSArch: runtime.GOARCH, + SKU: "personal_desktop", + } + + if got != want { + t.Errorf("got %q want %q", got, want) + } + + if response.Code != http.StatusOK { + t.Errorf("got HTTP %d want %d", response.Code, http.StatusOK) + } + }) + + t.Run("Sets SKU to 'suite' when in plugin mode", func(t *testing.T) { + model.Edition = "plugin" + request, _ := http.NewRequest(http.MethodGet, "/ping", nil) + response := httptest.NewRecorder() + + testAPI.handlePing(response, request) + + var got app.ServerMetadata + err := json.NewDecoder(response.Body).Decode(&got) + if err != nil { + t.Fatalf("Unable to JSON decode response body %q", response.Body) + } + + want := app.ServerMetadata{ + Version: model.CurrentVersion, + BuildNumber: model.BuildNumber, + BuildDate: model.BuildDate, + Commit: model.BuildHash, + Edition: "plugin", + DBType: "", + DBVersion: "", + OSType: runtime.GOOS, + OSArch: runtime.GOARCH, + SKU: "suite", + } + + if got != want { + t.Errorf("got %q want %q", got, want) + } + + if response.Code != http.StatusOK { + t.Errorf("got HTTP %d want %d", response.Code, http.StatusOK) + } + }) +} diff --git a/server/api/users.go b/server/api/users.go index 723ee66d6..90932d4d6 100644 --- a/server/api/users.go +++ b/server/api/users.go @@ -283,7 +283,7 @@ func (a *API) handleUpdateUserConfig(w http.ResponseWriter, r *http.Request) { // description: User config patch to apply // required: true // schema: - // "$ref": "#/definitions/UserPropPatch" + // "$ref": "#/definitions/UserPreferencesPatch" // security: // - BearerAuth: [] // responses: diff --git a/server/app/server_metadata.go b/server/app/server_metadata.go new file mode 100644 index 000000000..cd1e2f3d1 --- /dev/null +++ b/server/app/server_metadata.go @@ -0,0 +1,42 @@ +package app + +import ( + "runtime" + + "github.com/mattermost/focalboard/server/model" +) + +type ServerMetadata struct { + Version string `json:"version"` + BuildNumber string `json:"build_number"` + BuildDate string `json:"build_date"` + Commit string `json:"commit"` + Edition string `json:"edition"` + DBType string `json:"db_type"` + DBVersion string `json:"db_version"` + OSType string `json:"os_type"` + OSArch string `json:"os_arch"` + SKU string `json:"sku"` +} + +func (a *App) GetServerMetadata() *ServerMetadata { + var dbType string + var dbVersion string + if a != nil && a.store != nil { + dbType = a.store.DBType() + dbVersion = a.store.DBVersion() + } + + return &ServerMetadata{ + Version: model.CurrentVersion, + BuildNumber: model.BuildNumber, + BuildDate: model.BuildDate, + Commit: model.BuildHash, + Edition: model.Edition, + DBType: dbType, + DBVersion: dbVersion, + OSType: runtime.GOOS, + OSArch: runtime.GOARCH, + SKU: "personal_server", + } +} diff --git a/server/app/server_metadata_test.go b/server/app/server_metadata_test.go new file mode 100644 index 000000000..a7d54247b --- /dev/null +++ b/server/app/server_metadata_test.go @@ -0,0 +1,37 @@ +package app + +import ( + "reflect" + "runtime" + "testing" + + "github.com/mattermost/focalboard/server/model" +) + +func TestGetServerMetadata(t *testing.T) { + th, tearDown := SetupTestHelper(t) + defer tearDown() + + th.Store.EXPECT().DBType().Return("TEST_DB_TYPE") + th.Store.EXPECT().DBVersion().Return("TEST_DB_VERSION") + + t.Run("Get Server Metadata", func(t *testing.T) { + got := th.App.GetServerMetadata() + want := &ServerMetadata{ + Version: model.CurrentVersion, + BuildNumber: model.BuildNumber, + BuildDate: model.BuildDate, + Commit: model.BuildHash, + Edition: model.Edition, + DBType: "TEST_DB_TYPE", + DBVersion: "TEST_DB_VERSION", + OSType: runtime.GOOS, + OSArch: runtime.GOARCH, + SKU: "personal_server", + } + + if !reflect.DeepEqual(got, want) { + t.Errorf("got: %q, want: %q", got, want) + } + }) +} diff --git a/server/services/store/mockstore/mockstore.go b/server/services/store/mockstore/mockstore.go index 00a260353..0b9174ab6 100644 --- a/server/services/store/mockstore/mockstore.go +++ b/server/services/store/mockstore/mockstore.go @@ -182,6 +182,20 @@ func (mr *MockStoreMockRecorder) DBType() *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DBType", reflect.TypeOf((*MockStore)(nil).DBType)) } +// DBVersion mocks base method. +func (m *MockStore) DBVersion() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DBVersion") + ret0, _ := ret[0].(string) + return ret0 +} + +// DBVersion indicates an expected call of DBVersion. +func (mr *MockStoreMockRecorder) DBVersion() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DBVersion", reflect.TypeOf((*MockStore)(nil).DBVersion)) +} + // DeleteBlock mocks base method. func (m *MockStore) DeleteBlock(arg0, arg1 string) error { m.ctrl.T.Helper() diff --git a/server/services/store/sqlstore/public_methods.go b/server/services/store/sqlstore/public_methods.go index 422633f13..7cba92307 100644 --- a/server/services/store/sqlstore/public_methods.go +++ b/server/services/store/sqlstore/public_methods.go @@ -124,6 +124,11 @@ func (s *SQLStore) CreateUser(user *model.User) (*model.User, error) { } +func (s *SQLStore) DBVersion() string { + return s.dBVersion(s.db) + +} + func (s *SQLStore) DeleteBlock(blockID string, modifiedBy string) error { if s.dbType == model.SqliteDBType { return s.deleteBlock(s.db, blockID, modifiedBy) diff --git a/server/services/store/sqlstore/sqlstore.go b/server/services/store/sqlstore/sqlstore.go index bb09f2d28..35bb37c84 100644 --- a/server/services/store/sqlstore/sqlstore.go +++ b/server/services/store/sqlstore/sqlstore.go @@ -175,3 +175,26 @@ func (s *SQLStore) searchUserChannels(db sq.BaseRunner, teamID, userID, query st func (s *SQLStore) getChannel(db sq.BaseRunner, teamID, channel string) (*mmModel.Channel, error) { return nil, store.NewNotSupportedError("get channel not supported on standalone mode") } + +func (s *SQLStore) dBVersion(db sq.BaseRunner) string { + var version string + var row *sql.Row + + switch s.dbType { + case model.MysqlDBType: + row = s.db.QueryRow("SELECT VERSION()") + case model.PostgresDBType: + row = s.db.QueryRow("SHOW server_version") + case model.SqliteDBType: + row = s.db.QueryRow("SELECT sqlite_version()") + default: + return "" + } + + if err := row.Scan(&version); err != nil { + s.logger.Error("error checking database version", mlog.Err(err)) + return "" + } + + return version +} diff --git a/server/services/store/store.go b/server/services/store/store.go index bc8ec3e2e..a072a0e86 100644 --- a/server/services/store/store.go +++ b/server/services/store/store.go @@ -153,6 +153,7 @@ type Store interface { UpdateCardLimitTimestamp(cardLimit int) (int64, error) DBType() string + DBVersion() string GetLicense() *mmModel.License GetCloudLimits() (*mmModel.ProductLimits, error) diff --git a/server/swagger/docs/html/.openapi-generator/VERSION b/server/swagger/docs/html/.openapi-generator/VERSION index 6d54bbd77..0df17dd0f 100644 --- a/server/swagger/docs/html/.openapi-generator/VERSION +++ b/server/swagger/docs/html/.openapi-generator/VERSION @@ -1 +1 @@ -6.0.1 \ No newline at end of file +6.2.1 \ No newline at end of file diff --git a/server/swagger/docs/html/index.html b/server/swagger/docs/html/index.html index 81ff75c86..e3a8b6619 100644 --- a/server/swagger/docs/html/index.html +++ b/server/swagger/docs/html/index.html @@ -899,6 +899,9 @@ ul.nav-tabs {
  • createBoard
  • +
  • + createCard +
  • createCategory
  • @@ -941,6 +944,12 @@ ul.nav-tabs {
  • getBoards
  • +
  • + getCard +
  • +
  • + getCards +
  • getChannel
  • @@ -995,6 +1004,9 @@ ul.nav-tabs {
  • handleNotifyAdminUpgrade
  • +
  • + handleStatistics +
  • handleTeamBoardsInsights
  • @@ -1031,6 +1043,12 @@ ul.nav-tabs {
  • patchBoardsAndBlocks
  • +
  • + patchCard +
  • +
  • + ping +
  • postSharing
  • @@ -1497,16 +1515,7 @@ $(document).ready(function() {
    +
    + + + + + + +
    Query parameters
    + + + + + + + + + +
    NameDescription
    disable_notify + + +
    +
    +
    + + oas_any_type_not_mapped + + +
    +Disables notifications (for bulk data inserting) +
    +
    +
    +
    +
    + +

    Responses

    +

    +

    + + + + + + +
    +
    +
    + +
    + +
    +
    +

    +

    + + + + + + +
    +
    +
    + +
    + +
    +
    + +
    +
    @@ -4678,16 +5146,7 @@ $(document).ready(function() {
    + + + + + +
    +
    +
    + +
    + +
    +
    +

    +

    + + + + + + +
    +
    +
    + +
    + +
    +
    +
    +
    +
    +
    +
    +
    +

    getCards

    +

    Fetches cards for the specified board.

    +
    +
    +
    +

    +

    +

    +
    +
    /boards/{boardID}/cards
    +

    +

    Usage and SDK Samples

    +

    + + +
    +
    +
    curl -X GET \
    +-H "Authorization: [[apiKey]]" \
    + -H "Accept: application/json" \
    + "http://localhost/api/v2/boards/{boardID}/cards?page=56&per_page=56"
    +
    +
    +
    +
    import org.openapitools.client.*;
    +import org.openapitools.client.auth.*;
    +import org.openapitools.client.model.*;
    +import org.openapitools.client.api.DefaultApi;
    +
    +import java.io.File;
    +import java.util.*;
    +
    +public class DefaultApiExample {
    +    public static void main(String[] args) {
    +        ApiClient defaultClient = Configuration.getDefaultApiClient();
    +        
    +        // Configure API key authorization: BearerAuth
    +        ApiKeyAuth BearerAuth = (ApiKeyAuth) defaultClient.getAuthentication("BearerAuth");
    +        BearerAuth.setApiKey("YOUR API KEY");
    +        // Uncomment the following line to set a prefix for the API key, e.g. "Token" (defaults to null)
    +        //BearerAuth.setApiKeyPrefix("Token");
    +
    +        // Create an instance of the API class
    +        DefaultApi apiInstance = new DefaultApi();
    +        String boardID = boardID_example; // String | Board ID
    +        Integer page = 56; // Integer | The page to select (default=0)
    +        Integer perPage = 56; // Integer | Number of cards to return per page(default=100)
    +
    +        try {
    +            array[Object] result = apiInstance.getCards(boardID, page, perPage);
    +            System.out.println(result);
    +        } catch (ApiException e) {
    +            System.err.println("Exception when calling DefaultApi#getCards");
    +            e.printStackTrace();
    +        }
    +    }
    +}
    +
    +
    + +
    +
    import org.openapitools.client.api.DefaultApi;
    +
    +public class DefaultApiExample {
    +    public static void main(String[] args) {
    +        DefaultApi apiInstance = new DefaultApi();
    +        String boardID = boardID_example; // String | Board ID
    +        Integer page = 56; // Integer | The page to select (default=0)
    +        Integer perPage = 56; // Integer | Number of cards to return per page(default=100)
    +
    +        try {
    +            array[Object] result = apiInstance.getCards(boardID, page, perPage);
    +            System.out.println(result);
    +        } catch (ApiException e) {
    +            System.err.println("Exception when calling DefaultApi#getCards");
    +            e.printStackTrace();
    +        }
    +    }
    +}
    +
    + +
    +
    Configuration *apiConfig = [Configuration sharedConfig];
    +
    +// Configure API key authorization: (authentication scheme: BearerAuth)
    +[apiConfig setApiKey:@"YOUR_API_KEY" forApiKeyIdentifier:@"Authorization"];
    +// Uncomment below to setup prefix (e.g. Bearer) for API key, if needed
    +//[apiConfig setApiKeyPrefix:@"Bearer" forApiKeyIdentifier:@"Authorization"];
    +
    +
    +// Create an instance of the API class
    +DefaultApi *apiInstance = [[DefaultApi alloc] init];
    +String *boardID = boardID_example; // Board ID (default to null)
    +Integer *page = 56; // The page to select (default=0) (optional) (default to null)
    +Integer *perPage = 56; // Number of cards to return per page(default=100) (optional) (default to null)
    +
    +// Fetches cards for the specified board.
    +[apiInstance getCardsWith:boardID
    +    page:page
    +    perPage:perPage
    +              completionHandler: ^(array[Object] output, NSError* error) {
    +    if (output) {
    +        NSLog(@"%@", output);
    +    }
    +    if (error) {
    +        NSLog(@"Error: %@", error);
    +    }
    +}];
    +
    +
    + +
    +
    var FocalboardServer = require('focalboard_server');
    +var defaultClient = FocalboardServer.ApiClient.instance;
    +
    +// Configure API key authorization: BearerAuth
    +var BearerAuth = defaultClient.authentications['BearerAuth'];
    +BearerAuth.apiKey = "YOUR API KEY";
    +// Uncomment the following line to set a prefix for the API key, e.g. "Token" (defaults to null)
    +//BearerAuth.apiKeyPrefix['Authorization'] = "Token";
    +
    +// Create an instance of the API class
    +var api = new FocalboardServer.DefaultApi()
    +var boardID = boardID_example; // {String} Board ID
    +var opts = {
    +  'page': 56, // {Integer} The page to select (default=0)
    +  'perPage': 56 // {Integer} Number of cards to return per page(default=100)
    +};
    +
    +var callback = function(error, data, response) {
    +  if (error) {
    +    console.error(error);
    +  } else {
    +    console.log('API called successfully. Returned data: ' + data);
    +  }
    +};
    +api.getCards(boardID, opts, callback);
    +
    +
    + + +
    +
    using System;
    +using System.Diagnostics;
    +using Org.OpenAPITools.Api;
    +using Org.OpenAPITools.Client;
    +using Org.OpenAPITools.Model;
    +
    +namespace Example
    +{
    +    public class getCardsExample
    +    {
    +        public void main()
    +        {
    +            // Configure API key authorization: BearerAuth
    +            Configuration.Default.ApiKey.Add("Authorization", "YOUR_API_KEY");
    +            // Uncomment below to setup prefix (e.g. Bearer) for API key, if needed
    +            // Configuration.Default.ApiKeyPrefix.Add("Authorization", "Bearer");
    +
    +            // Create an instance of the API class
    +            var apiInstance = new DefaultApi();
    +            var boardID = boardID_example;  // String | Board ID (default to null)
    +            var page = 56;  // Integer | The page to select (default=0) (optional)  (default to null)
    +            var perPage = 56;  // Integer | Number of cards to return per page(default=100) (optional)  (default to null)
    +
    +            try {
    +                // Fetches cards for the specified board.
    +                array[Object] result = apiInstance.getCards(boardID, page, perPage);
    +                Debug.WriteLine(result);
    +            } catch (Exception e) {
    +                Debug.Print("Exception when calling DefaultApi.getCards: " + e.Message );
    +            }
    +        }
    +    }
    +}
    +
    +
    + +
    +
    <?php
    +require_once(__DIR__ . '/vendor/autoload.php');
    +
    +// Configure API key authorization: BearerAuth
    +OpenAPITools\Client\Configuration::getDefaultConfiguration()->setApiKey('Authorization', 'YOUR_API_KEY');
    +// Uncomment below to setup prefix (e.g. Bearer) for API key, if needed
    +// OpenAPITools\Client\Configuration::getDefaultConfiguration()->setApiKeyPrefix('Authorization', 'Bearer');
    +
    +// Create an instance of the API class
    +$api_instance = new OpenAPITools\Client\Api\DefaultApi();
    +$boardID = boardID_example; // String | Board ID
    +$page = 56; // Integer | The page to select (default=0)
    +$perPage = 56; // Integer | Number of cards to return per page(default=100)
    +
    +try {
    +    $result = $api_instance->getCards($boardID, $page, $perPage);
    +    print_r($result);
    +} catch (Exception $e) {
    +    echo 'Exception when calling DefaultApi->getCards: ', $e->getMessage(), PHP_EOL;
    +}
    +?>
    +
    + +
    +
    use Data::Dumper;
    +use WWW::OPenAPIClient::Configuration;
    +use WWW::OPenAPIClient::DefaultApi;
    +
    +# Configure API key authorization: BearerAuth
    +$WWW::OPenAPIClient::Configuration::api_key->{'Authorization'} = 'YOUR_API_KEY';
    +# uncomment below to setup prefix (e.g. Bearer) for API key, if needed
    +#$WWW::OPenAPIClient::Configuration::api_key_prefix->{'Authorization'} = "Bearer";
    +
    +# Create an instance of the API class
    +my $api_instance = WWW::OPenAPIClient::DefaultApi->new();
    +my $boardID = boardID_example; # String | Board ID
    +my $page = 56; # Integer | The page to select (default=0)
    +my $perPage = 56; # Integer | Number of cards to return per page(default=100)
    +
    +eval {
    +    my $result = $api_instance->getCards(boardID => $boardID, page => $page, perPage => $perPage);
    +    print Dumper($result);
    +};
    +if ($@) {
    +    warn "Exception when calling DefaultApi->getCards: $@\n";
    +}
    +
    + +
    +
    from __future__ import print_statement
    +import time
    +import openapi_client
    +from openapi_client.rest import ApiException
    +from pprint import pprint
    +
    +# Configure API key authorization: BearerAuth
    +openapi_client.configuration.api_key['Authorization'] = 'YOUR_API_KEY'
    +# Uncomment below to setup prefix (e.g. Bearer) for API key, if needed
    +# openapi_client.configuration.api_key_prefix['Authorization'] = 'Bearer'
    +
    +# Create an instance of the API class
    +api_instance = openapi_client.DefaultApi()
    +boardID = boardID_example # String | Board ID (default to null)
    +page = 56 # Integer | The page to select (default=0) (optional) (default to null)
    +perPage = 56 # Integer | Number of cards to return per page(default=100) (optional) (default to null)
    +
    +try:
    +    # Fetches cards for the specified board.
    +    api_response = api_instance.get_cards(boardID, page=page, perPage=perPage)
    +    pprint(api_response)
    +except ApiException as e:
    +    print("Exception when calling DefaultApi->getCards: %s\n" % e)
    +
    + +
    +
    extern crate DefaultApi;
    +
    +pub fn main() {
    +    let boardID = boardID_example; // String
    +    let page = 56; // Integer
    +    let perPage = 56; // Integer
    +
    +    let mut context = DefaultApi::Context::default();
    +    let result = client.getCards(boardID, page, perPage, &context).wait();
    +
    +    println!("{:?}", result);
    +}
    +
    +
    +
    + +

    Scopes

    + + +
    + +

    Parameters

    + +
    Path parameters
    + + + + + + + + + +
    NameDescription
    boardID* + + +
    +
    +
    + + String + + +
    +Board ID +
    +
    +
    + Required +
    +
    +
    +
    + + + + +
    Query parameters
    + + + + + + + + + + + + + +
    NameDescription
    page + + +
    +
    +
    + + Integer + + +
    +The page to select (default=0) +
    +
    +
    +
    +
    per_page + + +
    +
    +
    + + Integer + + +
    +Number of cards to return per page(default=100) +
    +
    +
    +
    +
    + +

    Responses

    +

    +

    + + + + + + +
    +
    +
    + +
    + +
    +
    +

    +

    + + + + + + +
    +
    +
    + +
    + +
    +
    +
    +
    +
    @@ -11221,19 +12452,7 @@ Channel ID
    + + + + + +
    +
    +
    + +
    + +
    +
    +

    +

    + + + + + + +
    +
    +
    + +
    + +
    +
    +
    +
    +
    @@ -19442,19 +20758,7 @@ limit for boards in a page.
    +
    + + + + + + +
    Query parameters
    + + + + + + + + + +
    NameDescription
    disable_notify + + +
    +
    +
    + + oas_any_type_not_mapped + + +
    +Disables notifications (for bulk data patching) +
    +
    +
    +
    +
    + +

    Responses

    +

    +

    + + + + + + +
    +
    +
    + +
    + +
    +
    +

    +

    + + + + + + +
    +
    +
    + +
    + +
    +
    +
    +
    +
    +
    +
    +
    +

    ping

    +

    Responds with server metadata if the web service is running.

    +
    +
    +
    +

    +

    +

    +
    +
    /ping
    +

    +

    Usage and SDK Samples

    +

    + + +
    +
    +
    curl -X GET \
    + "http://localhost/api/v2/ping"
    +
    +
    +
    +
    import org.openapitools.client.*;
    +import org.openapitools.client.auth.*;
    +import org.openapitools.client.model.*;
    +import org.openapitools.client.api.DefaultApi;
    +
    +import java.io.File;
    +import java.util.*;
    +
    +public class DefaultApiExample {
    +    public static void main(String[] args) {
    +
    +        // Create an instance of the API class
    +        DefaultApi apiInstance = new DefaultApi();
    +
    +        try {
    +            apiInstance.ping();
    +        } catch (ApiException e) {
    +            System.err.println("Exception when calling DefaultApi#ping");
    +            e.printStackTrace();
    +        }
    +    }
    +}
    +
    +
    + +
    +
    import org.openapitools.client.api.DefaultApi;
    +
    +public class DefaultApiExample {
    +    public static void main(String[] args) {
    +        DefaultApi apiInstance = new DefaultApi();
    +
    +        try {
    +            apiInstance.ping();
    +        } catch (ApiException e) {
    +            System.err.println("Exception when calling DefaultApi#ping");
    +            e.printStackTrace();
    +        }
    +    }
    +}
    +
    + +
    +
    
    +
    +// Create an instance of the API class
    +DefaultApi *apiInstance = [[DefaultApi alloc] init];
    +
    +// Responds with server metadata if the web service is running.
    +[apiInstance pingWithCompletionHandler: 
    +              ^(NSError* error) {
    +    if (error) {
    +        NSLog(@"Error: %@", error);
    +    }
    +}];
    +
    +
    + +
    +
    var FocalboardServer = require('focalboard_server');
    +
    +// Create an instance of the API class
    +var api = new FocalboardServer.DefaultApi()
    +var callback = function(error, data, response) {
    +  if (error) {
    +    console.error(error);
    +  } else {
    +    console.log('API called successfully.');
    +  }
    +};
    +api.ping(callback);
    +
    +
    + + +
    +
    using System;
    +using System.Diagnostics;
    +using Org.OpenAPITools.Api;
    +using Org.OpenAPITools.Client;
    +using Org.OpenAPITools.Model;
    +
    +namespace Example
    +{
    +    public class pingExample
    +    {
    +        public void main()
    +        {
    +
    +            // Create an instance of the API class
    +            var apiInstance = new DefaultApi();
    +
    +            try {
    +                // Responds with server metadata if the web service is running.
    +                apiInstance.ping();
    +            } catch (Exception e) {
    +                Debug.Print("Exception when calling DefaultApi.ping: " + e.Message );
    +            }
    +        }
    +    }
    +}
    +
    +
    + +
    +
    <?php
    +require_once(__DIR__ . '/vendor/autoload.php');
    +
    +// Create an instance of the API class
    +$api_instance = new OpenAPITools\Client\Api\DefaultApi();
    +
    +try {
    +    $api_instance->ping();
    +} catch (Exception $e) {
    +    echo 'Exception when calling DefaultApi->ping: ', $e->getMessage(), PHP_EOL;
    +}
    +?>
    +
    + +
    +
    use Data::Dumper;
    +use WWW::OPenAPIClient::Configuration;
    +use WWW::OPenAPIClient::DefaultApi;
    +
    +# Create an instance of the API class
    +my $api_instance = WWW::OPenAPIClient::DefaultApi->new();
    +
    +eval {
    +    $api_instance->ping();
    +};
    +if ($@) {
    +    warn "Exception when calling DefaultApi->ping: $@\n";
    +}
    +
    + +
    +
    from __future__ import print_statement
    +import time
    +import openapi_client
    +from openapi_client.rest import ApiException
    +from pprint import pprint
    +
    +# Create an instance of the API class
    +api_instance = openapi_client.DefaultApi()
    +
    +try:
    +    # Responds with server metadata if the web service is running.
    +    api_instance.ping()
    +except ApiException as e:
    +    print("Exception when calling DefaultApi->ping: %s\n" % e)
    +
    + +
    +
    extern crate DefaultApi;
    +
    +pub fn main() {
    +
    +    let mut context = DefaultApi::Context::default();
    +    let result = client.ping(&context).wait();
    +
    +    println!("{:?}", result);
    +}
    +
    +
    +
    + +

    Scopes

    + + +
    + +

    Parameters

    + + + + + + +

    Responses

    +

    +

    + + + + + + +
    +
    +
    +
    +
    @@ -24978,16 +26925,7 @@ $(document).ready(function() {