Skip to content

Commit

Permalink
(DOCSP-36597): C++ Template App, MVC style (#197)
Browse files Browse the repository at this point in the history
Add a C++ template app using FTXUI and the C++ SDK.

Ignore the C++ directory when running the Bluehawk copy script. Bluehawk
has a problem with using `::state:` in C++ code. For more details, refer
to this issue on the Bluehawk repo:
mongodb-university/Bluehawk#145

I used Bluehawk copy for all other files and manually copied the
`database_manager.cpp` where the code triggering this issue is located.

---------

Co-authored-by: Chris Bush <[email protected]>
  • Loading branch information
dacharyc and cbush authored Mar 29, 2024
1 parent 05978d6 commit 6958674
Show file tree
Hide file tree
Showing 62 changed files with 2,365 additions and 2 deletions.
10 changes: 10 additions & 0 deletions manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,16 @@
"repo_name": "realm-template-apps",
"backend_path": "sync-todo/v2/backend"
},
"cpp.todo.flex": {
"name": "C++ Todo App",
"repo_owner": "mongodb-university",
"repo_name": "realm-template-apps",
"backend_path": "sync-todo/v2/backend",
"client_path": "sync-todo/v2/generated/cpp",
"metadata_path": "sync-todo/v2/generated/cpp",
"metadata_filename": "atlasConfig",
"file_format": "json"
},
"react-native.todo.flex": {
"name": "React Native Todo App (TypeScript)",
"repo_owner": "mongodb-university",
Expand Down
7 changes: 5 additions & 2 deletions sync-todo/v2/bluehawk.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,13 @@

pushd "$(dirname "$0")"

rm -r generated

for client in client/*; do
# Can't Bluehawk copy the cpp client because of `:state:` in the code
# Can remove if block when this issue is resolved: https://github.com/mongodb-university/Bluehawk/issues/145
if [[ "$client" != "client/cpp"* ]]; then
rm -r generated/$(basename $client)
npx bluehawk copy -o generated/$(basename $client) $client/;
fi
done

cp template-atlasConfig.json generated/flutter/assets/config/atlasConfig.json
Expand Down
3 changes: 3 additions & 0 deletions sync-todo/v2/client/cpp/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
build/
.idea/
cmake-build-debug/
61 changes: 61 additions & 0 deletions sync-todo/v2/client/cpp/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
cmake_minimum_required(VERSION 3.25)

project(sync_todo)

set(CMAKE_CXX_STANDARD 17)

Include(FetchContent)

FetchContent_Declare(cpprealm
GIT_REPOSITORY https://github.com/realm/realm-cpp.git
GIT_TAG v1.0.0
)

FetchContent_Declare(ftxui
GIT_REPOSITORY https://github.com/ArthurSonzogni/ftxui
GIT_TAG v5.0.0
)

FetchContent_Declare(nlohmann_json
GIT_REPOSITORY https://github.com/nlohmann/json.git
GIT_TAG v3.11.3
)

FetchContent_GetProperties(ftxui)
if(NOT ftxui_POPULATED)
FetchContent_Populate(ftxui)
add_subdirectory(${ftxui_SOURCE_DIR} ${ftxui_BINARY_DIR} EXCLUDE_FROM_ALL)
endif()

FetchContent_MakeAvailable(cpprealm ftxui nlohmann_json)

add_executable(sync_todo
main.cpp
ss.hpp
controllers/app_controller.hpp
controllers/app_controller.cpp
controllers/controller.hpp
controllers/home_controller.hpp
controllers/home_controller.cpp
controllers/login_controller.hpp
controllers/login_controller.cpp
controllers/navigation.hpp
controllers/navigation.cpp
managers/auth_manager.hpp
managers/auth_manager.cpp
managers/database_manager.hpp
managers/database_manager.cpp
managers/error_manager.hpp
managers/error_manager.cpp
state/app_config_metadata.hpp
state/app_config_metadata.cpp
state/app_state.hpp
state/home_controller_state.hpp
state/item.hpp
views/scroller.hpp
views/scroller.cpp
)

target_link_libraries(sync_todo PRIVATE cpprealm)
target_link_libraries(sync_todo PRIVATE ftxui::component)
target_link_libraries(sync_todo PRIVATE nlohmann_json::nlohmann_json)
133 changes: 133 additions & 0 deletions sync-todo/v2/client/cpp/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
# C++ Template App

> [!NOTE]
> This is a Preview release of the C++ template app.
A todo list application built with the [Atlas Device SDK for C++](https://www.mongodb.com/docs/realm/sdk/cpp/) and [Atlas Device Sync](https://www.mongodb.com/docs/atlas/app-services/sync/).

This app uses a terminal UI built with [FTXUI](https://github.com/ArthurSonzogni/FTXUI).

## Configuration

For this template app to work, you must ensure that `atlasConfig.json` exists and contains the following properties:

- **appId:** your Atlas App Services App ID.
- **appUrl:** the URL to browse directly to your App Services App.
- **baseUrl:** the App Services backend URL. This should be https://services.cloud.mongodb.com in most cases.
- **clientApiBaseUrl:** the deployment region and baseURL a client application can use to connect to a specific regional
deployment. This information is not used by this template app.
- **dataApiBaseUrl:** the deployment region and Data API baseURL a client application can use to connect to a specific
Data API app in a given region. This information is not used by this template app.
- **dataExplorerLink:** the URL to browse to your collections in Atlas. Go here to see data syncing from your app.
- **dataSourceName:** the name of the data source in Atlas. This information is not used by this template app.

## Create an Atlas Device Sync Template App

### Create the Template App using App Services CLI

The easiest way to work with this client is to create the template app using App Services CLI. If you use the CLI to
create the template app, or if you use the CLI to pull the C++ client you created from the App Services UI, you'll get
a version of this client with the `atlasConfig.json` pre-populated with the appropriate values for your application.

For more information, refer to the [Template App documentation](https://www.mongodb.com/docs/atlas/app-services/reference/template-apps/).

### Cloning from GitHub

If you have cloned this repository from the GitHub
[mongodb/template-app-cpp-todo](https://github.com/mongodb/template-app-cpp-todo.git)
repository, you must create a separate App Services App with Device Sync
enabled to use this client. You can find information about how to do this
in the Atlas App Services documentation page:
[Template Apps -> Create a Template App](https://www.mongodb.com/docs/atlas/app-services/reference/template-apps/)

Once you have created the App Services App, replace the value in this client's `appId` field
in the `atlasConfig.json` file with your App Services App ID. For help finding this ID, refer
to: [Find Your Project or App ID](https://www.mongodb.com/docs/atlas/app-services/reference/find-your-project-or-app-id/)

### Making a Template App in the App Services UI

You can create a template app by logging on to [Atlas App Services](https://services.cloud.mongodb.com) and click the
**Create App From Template** button. Choose **Real Time Sync**, and then follow the prompts.

You can download some of the Sync clients from the UI as a .zip file. That is not possible at this time for the C++ client.
To get a copy of the C++ client with `atlasConfig.json` values pre-populated with the data for your app, pull a copy of
the template app client using App Services CLI. For more information, refer to the Atlas App Services documentation page:
[Template Apps -> Get a Template App Client](https://www.mongodb.com/docs/atlas/app-services/reference/template-apps/#get-a-template-app-client).

## Run the app

This app uses CMake to manage dependencies. You must have CMake installed to use this template app.

- Make a `/build` directory inside this client directory, and `cd` into it.

```shell
mkdir build && cd build
```

- Use CMake to create the Makefile

```shell
cmake ../
```

- Use CMake to build the app executable.

```shell
cmake --build .
```

- Run the executable. Pass the path to the `atlasConfig.json` as an argument when you run the application.

```shell
./sync_todo /path-to-file/atlasConfig.json
```

## Issues

Please report issues with the template at https://github.com/mongodb-university/realm-template-apps/issues/new

## Structure

To see the code examples concerned with Atlas Device SDK for C++, these files are the most relevant:

- Controllers:
- `app_controller.cpp`: Set up app state, including reading the `atlasConfig.json` and setting up the Atlas App connection.
- `home_controller.cpp`: Create the task list, add new items, and let the user change app state.
- Managers
- `auth_manager.cpp`: Authenticate users with the Atlas Device Sync app.
- `database_manager.cpp`: Handle Device Sync client configuration, Sync subscriptions, Sync sessions, and database CRUD operations.
- State
- `item.hpp`: The Atlas Device SDK data model.

```
├── controllers
│   ├── app_controller.cpp
│   ├── app_controller.hpp
│   ├── controller.hpp
│   ├── home_controller.cpp
│   ├── home_controller.hpp
│   ├── login_controller.cpp
│   ├── login_controller.hpp
│   ├── navigation.cpp
│   └── navigation.hpp
├── main.cpp
├── managers
│   ├── auth_manager.cpp
│   ├── auth_manager.hpp
│   ├── database_manager.cpp
│   ├── database_manager.hpp
│   ├── error_manager.cpp
│   └── error_manager.hpp
├── ss.hpp
├── state
│   ├── app_config_metadata.cpp
│   ├── app_config_metadata.hpp
│   ├── app_state.hpp
│   ├── home_controller_state.hpp
│   ├── item.hpp
│   ├── offline_mode_selection.hpp
│   └── subscription_selection.hpp
└── views
├── scroller.cpp
└── scroller.hpp
```
9 changes: 9 additions & 0 deletions sync-todo/v2/client/cpp/atlasConfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"appId": "INSERT-YOUR-APP-ID-HERE",
"appUrl": "https://services.cloud.mongodb.com/groups/6388cb218e4d865c34158da5/apps/65f46bd16a5a245b65769e5d",
"baseUrl": "https://services.cloud.mongodb.com",
"clientApiBaseUrl": "https://us-east-1.aws.services.cloud.mongodb.com",
"dataApiBaseUrl": "https://us-east-1.aws.data.mongodb-api.com",
"dataExplorerLink": "https://cloud.mongodb.com/links/6388cb218e4d865c34158da5/explorer/Cluster0/database/collection/find",
"dataSourceName": "mongodb-atlas"
}
62 changes: 62 additions & 0 deletions sync-todo/v2/client/cpp/controllers/app_controller.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
#include "app_controller.hpp"

void AppController::onFrame() {
_navigation.onFrame();
}

AppController::AppController(ftxui::ScreenInteractive *screen, std::string const& pathToAtlasConfig) {
_appState.screen = screen;

// Read the contents of the atlasConfig.json to get the metadata for the App Services App.
std::ifstream f(pathToAtlasConfig);
nlohmann::json data = nlohmann::json::parse(f);
auto appConfigMetadata = data.template get<AppConfigMetadata>();
f.close();

auto appConfig = realm::App::configuration {
.app_id = appConfigMetadata.appId
};
_appState.app = std::make_unique<realm::App>(appConfig);
_appState.authManager = std::make_unique<AuthManager>(this, _appState.app.get());
_appState.errorManager = std::make_unique<ErrorManager>(this);
_appState.appConfigMetadata = appConfigMetadata;

// Lay out and style the error modal.
auto dismissButton = ftxui::Button("Dismiss", [this]{ _appState.errorManager->clearError(); });
auto buttonLayout = ftxui::Container::Horizontal({ dismissButton });

_errorModal = Renderer(buttonLayout, [=] {
auto content = ftxui::vbox({
ftxui::hbox(ftxui::text(_appState.errorManager->getError().value()) | ftxui::hcenter),
ftxui::hbox(dismissButton->Render()) | ftxui::hcenter
}) | ftxui::center | size(ftxui::HEIGHT, ftxui::GREATER_THAN, 10);
return window(
ftxui::text(L" Error "), content) | ftxui::clear_under | ftxui::center | size(ftxui::WIDTH, ftxui::GREATER_THAN, 80);
});

component()->Add(_navigation.component());

if (_appState.app->get_current_user()) {
_navigation.goTo(std::make_unique<HomeController>(&_appState));
} else {
_navigation.goTo(std::make_unique<LoginController>(&_appState));
}
}

void AppController::onLoggedIn() {
_navigation.goTo(std::make_unique<HomeController>(&_appState));
}

void AppController::onLoggedOut() {
_navigation.goTo(std::make_unique<LoginController>(&_appState));
}

void AppController::onError(ErrorManager &error) {
_errorModal->Detach();
component()->Add(_errorModal);
_errorModal->TakeFocus();
}

void AppController::onErrorCleared(ErrorManager &error) {
_errorModal->Detach();
}
41 changes: 41 additions & 0 deletions sync-todo/v2/client/cpp/controllers/app_controller.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
#ifndef APP_CONTROLLER_HPP
#define APP_CONTROLLER_HPP

#include <nlohmann/json.hpp>
#include <cpprealm/sdk.hpp>
#include <fstream>

#include "ftxui/component/component.hpp"
#include "controller.hpp"
#include "home_controller.hpp"
#include "login_controller.hpp"
#include "navigation.hpp"
#include "../managers/auth_manager.hpp"
#include "../managers/error_manager.hpp"
#include "../state/app_config_metadata.hpp"
#include "../state/app_state.hpp"
#include "../state/home_controller_state.hpp"
#include "../ss.hpp"

class AppController final : public Controller, public AuthManager::Delegate, public ErrorManager::Delegate {
private:
AppState _appState;
Navigation _navigation;
ftxui::Component _errorModal;

public:
explicit AppController(ftxui::ScreenInteractive *screen, std::string const& pathToAtlasConfig);

void onFrame() override;

private:
void onLoggedIn() override;

void onLoggedOut() override;

void onError(ErrorManager &error) override;

void onErrorCleared(ErrorManager &error) override;
};

#endif
23 changes: 23 additions & 0 deletions sync-todo/v2/client/cpp/controllers/controller.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#ifndef CONTROLLER_HPP
#define CONTROLLER_HPP

#include "ftxui/component/component.hpp"

class Controller {
private:
ftxui::Component _component;

public:
Controller(): Controller(ftxui::Container::Stacked({})) {}
Controller(ftxui::Component component): _component(component) {}
virtual ~Controller() = 0;
virtual void onFrame() {}

ftxui::Component component() {
return _component;
}
};

inline Controller::~Controller() = default;

#endif
Loading

0 comments on commit 6958674

Please sign in to comment.