Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# ensure shell scripts always use LF line endings (WSL compatible)
*.sh text eol=lf
*.bash text eol=lf

1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
misc
.DS_Store
pocket-ic
24 changes: 21 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,24 @@
[![Platform](https://img.shields.io/badge/Platform-Healthcare-red.svg?style=flat)](http://medblock.id/)

## 📘 Overview

Medblock is an Electronic Medical Record (EMR) registry system that leverages the power of the Internet Computer to provide secure and efficient healthcare solutions. The project is organized into three main canisters, each designed to fulfill specific responsibilities:

1. **Patient Registry**: Manages comprehensive patient information and records, ensuring easy access by authorized users.
2. **EMR Registry**: Centralizes electronic medical records, facilitating efficient data management and retrieval for healthcare providers.
3. **Provider Registry**: Handles essential information related to healthcare providers, including their credentials and specialties.

## 🏗️ Architecture Diagram

The architectural diagram below provides a visual representation of how the Medblock project is structured and how its components interact. This illustration assists in understanding the relationships and data flow between the Mobile App, various canisters, and the dashboards.
![](final_demo/pwa/public/arsitektur.png)

## 📋 Getting Started

### Prerequisites

Ensure you have the following tools installed to work with the Medblock a project:

- **Rust `1.27.0`**: A systems programming language focused on safety and performance.
- **Next.js `14.2.6`**: A React framework for building server-rendered applications.
- **Tailwind CSS `3.4.1`**: A utility-first CSS framework for styling.
Expand All @@ -41,7 +46,8 @@ Before diving into development, install the essential tools on your system:
```

### Initial Setup
To set up the environment, execute the `setup.sh` script. This script automates the following tasks:

To set up the environment, execute the `./canister/setup.sh` script. This script automates the following tasks:

- Verifies the installation of the required tools.
- Creates unique canister IDs for each registry.
Expand All @@ -51,9 +57,11 @@ To set up the environment, execute the `setup.sh` script. This script automates
> **Note**: The setup may take a few minutes depending on your system specifications.

### Development Workflow

When developing a feature for a canister, use the build.sh script. This script automatically regenerates the candid interface for the canister you're working on and recompiles the EMR registry canister, as the other two canisters depend on it. If you've already run the setup script, this step should be quick. Use the --all flag with the script to rebuild all canisters simultaneously.

### Deploy Locally

To deploy the canisters locally, navigate to the `scripts/deployments` directory and run the `local.sh` script:

```bash
Expand All @@ -72,6 +80,7 @@ The `local.sh` script performs the following actions:
### Running Tests

#### Unit Tests

To run unit tests, compile and execute them using the --release flag to ensure optimal performance:

```bash
Expand All @@ -80,10 +89,12 @@ cargo test --release
```

#### Integration Tests

For integration testing, follow these steps:

1. Install **Pocket-IC** and follow the installation guidelines.
2. Build all canisters with the `--all` flag:

```bash
./build.sh --all
```
Expand All @@ -97,7 +108,9 @@ For integration testing, follow these steps:
## Testing

### Integration Tests

To run the integration tests for the canister:

```bash
# Run all integration tests in release mode
cargo integration-test
Expand All @@ -113,31 +126,36 @@ cargo integration-test -- --nocapture
```

The integration tests are located in `canister/tests/integration-tests/` and test the full functionality of the canisters including:

- Group management
- EMR access control
- Patient registration
- Provider interactions

### Unit Tests

For unit tests, you can use the standard cargo test command in each canister directory:

```bash
cd canister/src/patient_registry
cargo test
```

### Private VPS Node

We have setup a private VPS node to act as a psuedo-staging branch for the canisters.

You must have sshpass installed first!
You must have sshpass installed first!

```bash
sudo apt-get install sshpass # linux
brew install sshpass # mac
```

To connect to the node instead of local or mainnet, you can use the following command:

```bash
./portforward.sh
```

This will forward the port to the node hosted in the VPS and you can interact with the node as if it were local.
This will forward the port to the node hosted in the VPS and you can interact with the node as if it were local.
50 changes: 40 additions & 10 deletions canister/build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -52,23 +52,49 @@ build_canister() {
local start_time=$(date +%s.%N)

log_process "Building ${MAGENTA}$canister_name${NC} canister..."
dfx build $canister_name >/dev/null 2>&1
# ensure we're in the canister directory (cross-platform compatible)
cd "$root" || {
echo -e "${RED}[ERROR]${NC} Failed to change to directory: $root"
return 1
}

# show build output on failure, hide on success
if ! dfx build $canister_name 2>&1; then
echo -e "${RED}[ERROR]${NC} Failed to build ${MAGENTA}$canister_name${NC} canister"
echo -e "${YELLOW}[INFO]${NC} Make sure you're running from the canister directory and dfx is running"
return 1
fi

# check if wasm file exists before proceeding
if [ ! -f "$wasm_path" ]; then
echo -e "${RED}[ERROR]${NC} WASM file not found: $wasm_path"
echo -e "${YELLOW}[INFO]${NC} Build may have failed. Check dfx build output above."
return 1
fi

log_info "Inserting placeholder candid..."
echo "$dummy_did" >$did_path

log_process "Extracting candid from wasm..."
candid-extractor $wasm_path >$did_path
if ! candid-extractor $wasm_path >$did_path 2>/dev/null; then
echo -e "${RED}[ERROR]${NC} Failed to extract candid from $wasm_path"
return 1
fi

# add candid metadata and shrink wasm
log_process "Processing WASM file..."
ic-wasm "$wasm_path" \
if ! ic-wasm "$wasm_path" \
-o "$wasm_path" \
metadata candid:service -v public -f $did_path
metadata candid:service -v public -f $did_path 2>/dev/null; then
echo -e "${RED}[ERROR]${NC} Failed to add candid metadata to WASM"
return 1
fi

ic-wasm "$wasm_path" \
if ! ic-wasm "$wasm_path" \
-o "$wasm_path" \
shrink
shrink 2>/dev/null; then
echo -e "${YELLOW}[WARNING]${NC} Failed to shrink WASM (non-fatal)"
fi

local end_time=$(date +%s.%N)
local build_time=$(echo "$end_time - $start_time" | bc)
Expand Down Expand Up @@ -99,13 +125,17 @@ if [ -z "$canister" ]; then
exit 0
fi

cd $root
# ensure we're in the canister directory from the start
cd "$root" || {
echo -e "${RED}[ERROR]${NC} Failed to change to directory: $root"
exit 1
}

total_start_time=$(date +%s.%N)

# build EMR registry first since other canisters depend on it
if [ "$canister" == "$emr_canister" ]; then
build_canister $emr_canister
build_canister $emr_canister || exit 1
total_end_time=$(date +%s.%N)
total_time=$(echo "$total_end_time - $total_start_time" | bc)
log_success "EMR Registry build completed in ${GREEN}$(printf "%.2f" $total_time)s${NC}"
Expand All @@ -114,10 +144,10 @@ fi

# for other canisters, build EMR registry first then build target canister
log_warning "EMR Registry is a dependency, building it first..."
build_canister $emr_canister
build_canister $emr_canister || exit 1

log_info "Building target canister..."
build_canister $canister
build_canister $canister || exit 1

total_end_time=$(date +%s.%N)
total_time=$(echo "$total_end_time - $total_start_time" | bc)
Expand Down
91 changes: 86 additions & 5 deletions canister/scripts/deployments/local.sh
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
#!/bin/bash

# ensure script is run with bash (cross-platform compatible)
if [ -z "$BASH_VERSION" ]; then
echo "Error: This script must be run with bash, not sh"
echo "Usage: bash $0"
exit 1
fi

# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
Expand All @@ -14,25 +21,99 @@ cd $root/canister
bash $root/canister/setup.sh
# This script deploys the canister locally.
FE_PORT=4943
lsof -i tcp:${FE_PORT} | awk 'NR!=1 {print $2}' | xargs kill || true

# stop dfx first to ensure clean state (cross-platform compatible)
echo -e "${BLUE}[INFO]${NC} Stopping any existing dfx instances..."
dfx stop >/dev/null 2>&1 || true

# kill any existing processes on FE_PORT (cross-platform compatible)
pids=$(lsof -i tcp:${FE_PORT} 2>/dev/null | awk 'NR!=1 {print $2}')
if [ -n "$pids" ]; then
echo "$pids" | xargs kill 2>/dev/null || true
sleep 2
fi

# check if PocketIC is installed (needed for integration tests)
check_pocketic() {
local pocketic_path
# use which to find exact path, ensuring it's actually pocket-ic binary
pocketic_path=$(which pocket-ic 2>/dev/null)
if [ -n "$pocketic_path" ] && [ -x "$pocketic_path" ] && [ "$(basename "$pocketic_path")" = "pocket-ic" ]; then
echo -e "${GREEN}[INFO]${NC} PocketIC found: $pocketic_path"
return 0
else
echo -e "${YELLOW}[WARNING]${NC} PocketIC not found in PATH"
echo -e "${YELLOW}[INFO]${NC} PocketIC is optional for local development but required for integration tests"
echo -e "${YELLOW}[INFO]${NC} Install from: https://github.com/dfinity/pocketic"
return 1
fi
}

# cleanup any existing PocketIC processes (cross-platform compatible)
cleanup_pocketic() {
# find and kill any running pocket-ic processes
if command -v pgrep &>/dev/null; then
# use pgrep if available (Linux/WSL)
pocketic_pids=$(pgrep -f "pocket-ic" 2>/dev/null)
else
# fallback for macOS (no pgrep)
pocketic_pids=$(ps aux | grep -i "[p]ocket-ic" | awk '{print $2}' 2>/dev/null)
fi

if [ -n "$pocketic_pids" ]; then
echo -e "${BLUE}[INFO]${NC} Cleaning up existing PocketIC processes..."
echo "$pocketic_pids" | xargs kill 2>/dev/null || true
sleep 1
fi
}

# ensure canisters exist before installing (cross-platform compatible)
ensure_canisters_exist() {
echo -e "${BLUE}[INFO]${NC} Ensuring canisters are created..."
dfx canister create emr_registry 2>/dev/null || true
dfx canister create patient_registry 2>/dev/null || true
dfx canister create provider_registry 2>/dev/null || true
}

check_pocketic
cleanup_pocketic

# wait for dfx to be ready (cross-platform polling)
wait_for_dfx() {
echo -e "${YELLOW}[WAIT]${NC} Waiting for dfx to start..."
max_attempts=60
attempt=0
while [ $attempt -lt $max_attempts ]; do
if dfx ping >/dev/null 2>&1; then
return 0
fi
sleep 1
attempt=$((attempt + 1))
done
echo -e "${RED}[ERROR]${NC} dfx failed to start within $max_attempts seconds"
return 1
}

# Check if --background flag is passed
if [[ "$1" == "--background" ]]; then
echo -e "${BLUE}[INFO]${NC} Starting dfx in background mode..."
dfx start --background
wait_for_dfx || exit 1
else
echo -e "${BLUE}[INFO]${NC} Starting dfx in concurrent mode..."
# Start dfx in the background but keep output visible
(dfx start 2>&1 | sed 's/^/[CANISTER] /') &
# filter out harmless PocketIC panic messages (cross-platform compatible)
(dfx start 2>&1 | grep -v -E "(panicked at|SendError|Error from launcher process.*exited due to signal)" | sed 's/^/[CANISTER] /') &
DFX_PID=$!
# Store the PID so we can terminate it later if needed
echo $DFX_PID > /tmp/dfx.pid

# Wait for dfx to initialize
echo -e "${YELLOW}[WAIT]${NC} Waiting for dfx to start..."
sleep 5
wait_for_dfx || exit 1
fi

# ensure canisters exist before installing
ensure_canisters_exist

echo -e "${GREEN}[INFO]${NC} Installing canisters..."
dfx canister install emr_registry --wasm $root/canister/target/wasm32-unknown-unknown/release/emr_registry.wasm --mode=install -y

Expand Down
2 changes: 1 addition & 1 deletion canister/scripts/deployments/reinstall-staging.sh
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#! bash
#!/bin/bash

# Get the directory of the current script
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
Expand Down
2 changes: 1 addition & 1 deletion canister/scripts/upgrades/staging/upgrade-emr.sh
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#! bash
#!/bin/bash
root=$(git rev-parse --show-toplevel)

bash $root/canister/build.sh emr_registry
Expand Down
2 changes: 1 addition & 1 deletion canister/scripts/upgrades/staging/upgrade-patient.sh
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#! bash
#!/bin/bash
root=$(git rev-parse --show-toplevel)

bash $root/canister/build.sh patient_registry
Expand Down
2 changes: 1 addition & 1 deletion canister/scripts/upgrades/staging/upgrade-provider.sh
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#! bash
#!/bin/bash
root=$(git rev-parse --show-toplevel)

bash $root/canister/build.sh provider_registry
Expand Down
2 changes: 1 addition & 1 deletion canister/scripts/utils/add_controller.sh
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#! bash
#!/bin/bash
root=$(git rev-parse --show-toplevel)
cd $root/canister
canister=$1
Expand Down
2 changes: 1 addition & 1 deletion canister/scripts/utils/add_controller_all.sh
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#! bash
#!/bin/bash
root=$(git rev-parse --show-toplevel)
cd $root/canister
controller=$1
Expand Down
2 changes: 1 addition & 1 deletion canister/scripts/utils/add_metrics_collector.sh
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#! bash
#!/bin/bash
root=$(git rev-parse --show-toplevel)
cd $root/canister
canister=$1
Expand Down
2 changes: 1 addition & 1 deletion canister/scripts/utils/add_provider.sh
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#!/bin/bash
# TODO
#! bash
root=$(git rev-parse --show-toplevel)
cd $root/canister
caller=$1
Expand Down
2 changes: 1 addition & 1 deletion canister/scripts/utils/log.sh
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#! bash
#!/bin/bash

# colors
RED='\033[0;31m'
Expand Down
Loading