diff --git a/Cargo.toml b/Cargo.toml index e371d29..8381944 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,6 +4,7 @@ members = [ "contracts/governance", "contracts/insurance", "contracts/teachlink", + "contracts/documentation", ] [workspace.package] diff --git a/contracts/documentation/Cargo.toml b/contracts/documentation/Cargo.toml new file mode 100644 index 0000000..ce84467 --- /dev/null +++ b/contracts/documentation/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "teachlink_documentation" +version = "0.1.0" +edition.workspace = true +repository.workspace = true +license.workspace = true + +[lib] +crate-type = ["cdylib", "rlib"] + +[dependencies] +soroban-sdk.workspace = true + +[dev-dependencies] +soroban-sdk = { workspace = true, features = ["testutils"] } + +[features] +testutils = ["soroban-sdk/testutils"] + +[lints] +workspace = true diff --git a/contracts/documentation/src/lib.rs b/contracts/documentation/src/lib.rs new file mode 100644 index 0000000..11c62ce --- /dev/null +++ b/contracts/documentation/src/lib.rs @@ -0,0 +1,233 @@ +//! TeachLink Documentation Contract +//! +//! A smart contract for managing documentation, knowledge base articles, +//! FAQs, tutorials, and community-contributed content. + +#![no_std] + +use soroban_sdk::{contract, contractimpl, contracttype, Address, Env, String, Vec}; + +/// Documentation category types +#[contracttype] +#[derive(Clone, Debug, Eq, PartialEq)] +pub enum DocCategory { + Guide, + ApiReference, + Tutorial, + Faq, + KnowledgeBase, + Troubleshooting, +} + +/// Content visibility levels +#[contracttype] +#[derive(Clone, Debug, Eq, PartialEq)] +pub enum Visibility { + Public, + Community, + Private, +} + +/// A documentation article +#[contracttype] +#[derive(Clone)] +pub struct Article { + pub id: String, + pub title: String, + pub content: String, + pub category: DocCategory, + pub language: String, + pub version: u32, + pub author: Address, + pub visibility: Visibility, + pub tags: Vec, + pub created_at: u64, + pub updated_at: u64, + pub view_count: u64, + pub helpful_count: u64, +} + +/// A FAQ entry +#[contracttype] +#[derive(Clone)] +pub struct FaqEntry { + pub id: String, + pub question: String, + pub answer: String, + pub category: String, + pub language: String, + pub author: Address, + pub created_at: u64, + pub updated_at: u64, + pub helpful_count: u64, +} + +/// Documentation contract storage keys +#[contracttype] +enum DocKey { + ArticleCount, + FaqCount, + Version, +} + +/// Main documentation contract +#[contract] +pub struct DocumentationContract; + +#[contractimpl] +impl DocumentationContract { + /// Create a new documentation article + pub fn create_article( + env: Env, + id: String, + title: String, + content: String, + category: DocCategory, + language: String, + tags: Vec, + visibility: Visibility, + author: Address, + ) -> Article { + let timestamp = env.ledger().timestamp(); + + let article = Article { + id: id.clone(), + title, + content, + category, + language, + version: 1, + author, + visibility, + tags, + created_at: timestamp, + updated_at: timestamp, + view_count: 0, + helpful_count: 0, + }; + + env.storage().instance().set(&id, &article); + + // Increment article count + let count_key = DocKey::ArticleCount; + let current_count: u64 = env.storage().instance().get(&count_key).unwrap_or(0); + env.storage() + .instance() + .set(&count_key, &(current_count + 1)); + + article + } + + /// Get an article by ID + pub fn get_article(env: Env, id: String) -> Article { + env.storage().instance().get(&id).unwrap() + } + + /// Update an existing article + pub fn update_article( + env: Env, + id: String, + title: String, + content: String, + tags: Vec, + ) -> Article { + let mut article: Article = env.storage().instance().get(&id).unwrap(); + + article.title = title; + article.content = content; + article.tags = tags; + article.version += 1; + article.updated_at = env.ledger().timestamp(); + + env.storage().instance().set(&id, &article); + + article + } + + /// Record a view for analytics + pub fn record_view(env: Env, article_id: String) { + let mut article: Article = env.storage().instance().get(&article_id).unwrap(); + article.view_count += 1; + + env.storage().instance().set(&article_id, &article); + } + + /// Record that a user found an article helpful + pub fn mark_helpful(env: Env, article_id: String) { + let mut article: Article = env.storage().instance().get(&article_id).unwrap(); + article.helpful_count += 1; + + env.storage().instance().set(&article_id, &article); + } + + /// Create a new FAQ entry + pub fn create_faq( + env: Env, + id: String, + question: String, + answer: String, + category: String, + language: String, + author: Address, + ) -> FaqEntry { + let timestamp = env.ledger().timestamp(); + + let faq = FaqEntry { + id: id.clone(), + question, + answer, + category, + language, + author, + created_at: timestamp, + updated_at: timestamp, + helpful_count: 0, + }; + + env.storage().instance().set(&id, &faq); + + // Increment FAQ count + let count_key = DocKey::FaqCount; + let current_count: u64 = env.storage().instance().get(&count_key).unwrap_or(0); + env.storage() + .instance() + .set(&count_key, &(current_count + 1)); + + faq + } + + /// Get FAQ by ID + pub fn get_faq(env: Env, id: String) -> FaqEntry { + env.storage().instance().get(&id).unwrap() + } + + /// Search articles by keyword (simplified implementation) + pub fn search_articles(env: Env, _query: String) -> Vec
{ + // In a full implementation, this would search through articles + // For now, return empty vector as placeholder + Vec::new(&env) + } + + /// Get total article count + pub fn get_article_count(env: Env) -> u64 { + env.storage() + .instance() + .get(&DocKey::ArticleCount) + .unwrap_or(0) + } + + /// Get total FAQ count + pub fn get_faq_count(env: Env) -> u64 { + env.storage().instance().get(&DocKey::FaqCount).unwrap_or(0) + } + + /// Get current documentation version + pub fn get_version(env: Env) -> u32 { + env.storage().instance().get(&DocKey::Version).unwrap_or(1) + } + + /// Update documentation version + pub fn update_version(env: Env, version: u32) { + env.storage().instance().set(&DocKey::Version, &version); + } +} diff --git a/contracts/documentation/test_snapshots/test_article_counts.1.json b/contracts/documentation/test_snapshots/test_article_counts.1.json new file mode 100644 index 0000000..4ac9e3e --- /dev/null +++ b/contracts/documentation/test_snapshots/test_article_counts.1.json @@ -0,0 +1,318 @@ +{ + "generators": { + "address": 2, + "nonce": 0, + "mux_id": 0 + }, + "auth": [ + [], + [], + [], + [] + ], + "ledger": { + "protocol_version": 25, + "sequence_number": 0, + "timestamp": 0, + "network_id": "0000000000000000000000000000000000000000000000000000000000000000", + "base_reserve": 0, + "min_persistent_entry_ttl": 4096, + "min_temp_entry_ttl": 16, + "max_entry_ttl": 6312000, + "ledger_entries": [ + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": "ledger_key_contract_instance", + "durability": "persistent", + "val": { + "contract_instance": { + "executable": { + "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "storage": [ + { + "key": { + "string": "count-001" + }, + "val": { + "map": [ + { + "key": { + "symbol": "author" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "category" + }, + "val": { + "vec": [ + { + "symbol": "Guide" + } + ] + } + }, + { + "key": { + "symbol": "content" + }, + "val": { + "string": "Content 1" + } + }, + { + "key": { + "symbol": "created_at" + }, + "val": { + "u64": "0" + } + }, + { + "key": { + "symbol": "helpful_count" + }, + "val": { + "u64": "0" + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "string": "count-001" + } + }, + { + "key": { + "symbol": "language" + }, + "val": { + "string": "en" + } + }, + { + "key": { + "symbol": "tags" + }, + "val": { + "vec": [] + } + }, + { + "key": { + "symbol": "title" + }, + "val": { + "string": "Article 1" + } + }, + { + "key": { + "symbol": "updated_at" + }, + "val": { + "u64": "0" + } + }, + { + "key": { + "symbol": "version" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "view_count" + }, + "val": { + "u64": "0" + } + }, + { + "key": { + "symbol": "visibility" + }, + "val": { + "vec": [ + { + "symbol": "Public" + } + ] + } + } + ] + } + }, + { + "key": { + "string": "count-002" + }, + "val": { + "map": [ + { + "key": { + "symbol": "author" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "category" + }, + "val": { + "vec": [ + { + "symbol": "Guide" + } + ] + } + }, + { + "key": { + "symbol": "content" + }, + "val": { + "string": "Content 2" + } + }, + { + "key": { + "symbol": "created_at" + }, + "val": { + "u64": "0" + } + }, + { + "key": { + "symbol": "helpful_count" + }, + "val": { + "u64": "0" + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "string": "count-002" + } + }, + { + "key": { + "symbol": "language" + }, + "val": { + "string": "en" + } + }, + { + "key": { + "symbol": "tags" + }, + "val": { + "vec": [] + } + }, + { + "key": { + "symbol": "title" + }, + "val": { + "string": "Article 2" + } + }, + { + "key": { + "symbol": "updated_at" + }, + "val": { + "u64": "0" + } + }, + { + "key": { + "symbol": "version" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "view_count" + }, + "val": { + "u64": "0" + } + }, + { + "key": { + "symbol": "visibility" + }, + "val": { + "vec": [ + { + "symbol": "Public" + } + ] + } + } + ] + } + }, + { + "key": { + "vec": [ + { + "symbol": "ArticleCount" + } + ] + }, + "val": { + "u64": "2" + } + } + ] + } + } + } + }, + "ext": "v0" + }, + "live_until": 4095 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_code": { + "ext": "v0", + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "code": "" + } + }, + "ext": "v0" + }, + "live_until": 4095 + } + ] + }, + "events": [] +} \ No newline at end of file diff --git a/contracts/documentation/test_snapshots/test_create_api_reference.1.json b/contracts/documentation/test_snapshots/test_create_api_reference.1.json new file mode 100644 index 0000000..8b7cbfd --- /dev/null +++ b/contracts/documentation/test_snapshots/test_create_api_reference.1.json @@ -0,0 +1,202 @@ +{ + "generators": { + "address": 2, + "nonce": 0, + "mux_id": 0 + }, + "auth": [ + [], + [] + ], + "ledger": { + "protocol_version": 25, + "sequence_number": 0, + "timestamp": 0, + "network_id": "0000000000000000000000000000000000000000000000000000000000000000", + "base_reserve": 0, + "min_persistent_entry_ttl": 4096, + "min_temp_entry_ttl": 16, + "max_entry_ttl": 6312000, + "ledger_entries": [ + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": "ledger_key_contract_instance", + "durability": "persistent", + "val": { + "contract_instance": { + "executable": { + "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "storage": [ + { + "key": { + "string": "api-001" + }, + "val": { + "map": [ + { + "key": { + "symbol": "author" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "category" + }, + "val": { + "vec": [ + { + "symbol": "ApiReference" + } + ] + } + }, + { + "key": { + "symbol": "content" + }, + "val": { + "string": "## bridge_out\\n\\nLock tokens for bridging..." + } + }, + { + "key": { + "symbol": "created_at" + }, + "val": { + "u64": "0" + } + }, + { + "key": { + "symbol": "helpful_count" + }, + "val": { + "u64": "0" + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "string": "api-001" + } + }, + { + "key": { + "symbol": "language" + }, + "val": { + "string": "en" + } + }, + { + "key": { + "symbol": "tags" + }, + "val": { + "vec": [ + { + "string": "bridge" + }, + { + "string": "api" + } + ] + } + }, + { + "key": { + "symbol": "title" + }, + "val": { + "string": "Bridge API Reference" + } + }, + { + "key": { + "symbol": "updated_at" + }, + "val": { + "u64": "0" + } + }, + { + "key": { + "symbol": "version" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "view_count" + }, + "val": { + "u64": "0" + } + }, + { + "key": { + "symbol": "visibility" + }, + "val": { + "vec": [ + { + "symbol": "Public" + } + ] + } + } + ] + } + }, + { + "key": { + "vec": [ + { + "symbol": "ArticleCount" + } + ] + }, + "val": { + "u64": "1" + } + } + ] + } + } + } + }, + "ext": "v0" + }, + "live_until": 4095 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_code": { + "ext": "v0", + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "code": "" + } + }, + "ext": "v0" + }, + "live_until": 4095 + } + ] + }, + "events": [] +} \ No newline at end of file diff --git a/contracts/documentation/test_snapshots/test_create_faq.1.json b/contracts/documentation/test_snapshots/test_create_faq.1.json new file mode 100644 index 0000000..ec2aab0 --- /dev/null +++ b/contracts/documentation/test_snapshots/test_create_faq.1.json @@ -0,0 +1,155 @@ +{ + "generators": { + "address": 2, + "nonce": 0, + "mux_id": 0 + }, + "auth": [ + [], + [] + ], + "ledger": { + "protocol_version": 25, + "sequence_number": 0, + "timestamp": 0, + "network_id": "0000000000000000000000000000000000000000000000000000000000000000", + "base_reserve": 0, + "min_persistent_entry_ttl": 4096, + "min_temp_entry_ttl": 16, + "max_entry_ttl": 6312000, + "ledger_entries": [ + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": "ledger_key_contract_instance", + "durability": "persistent", + "val": { + "contract_instance": { + "executable": { + "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "storage": [ + { + "key": { + "string": "faq-001" + }, + "val": { + "map": [ + { + "key": { + "symbol": "answer" + }, + "val": { + "string": "TeachLink is a decentralized knowledge-sharing platform..." + } + }, + { + "key": { + "symbol": "author" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "category" + }, + "val": { + "string": "general" + } + }, + { + "key": { + "symbol": "created_at" + }, + "val": { + "u64": "0" + } + }, + { + "key": { + "symbol": "helpful_count" + }, + "val": { + "u64": "0" + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "string": "faq-001" + } + }, + { + "key": { + "symbol": "language" + }, + "val": { + "string": "en" + } + }, + { + "key": { + "symbol": "question" + }, + "val": { + "string": "What is TeachLink?" + } + }, + { + "key": { + "symbol": "updated_at" + }, + "val": { + "u64": "0" + } + } + ] + } + }, + { + "key": { + "vec": [ + { + "symbol": "FaqCount" + } + ] + }, + "val": { + "u64": "1" + } + } + ] + } + } + } + }, + "ext": "v0" + }, + "live_until": 4095 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_code": { + "ext": "v0", + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "code": "" + } + }, + "ext": "v0" + }, + "live_until": 4095 + } + ] + }, + "events": [] +} \ No newline at end of file diff --git a/contracts/documentation/test_snapshots/test_create_guide_article.1.json b/contracts/documentation/test_snapshots/test_create_guide_article.1.json new file mode 100644 index 0000000..40ed765 --- /dev/null +++ b/contracts/documentation/test_snapshots/test_create_guide_article.1.json @@ -0,0 +1,202 @@ +{ + "generators": { + "address": 2, + "nonce": 0, + "mux_id": 0 + }, + "auth": [ + [], + [] + ], + "ledger": { + "protocol_version": 25, + "sequence_number": 0, + "timestamp": 0, + "network_id": "0000000000000000000000000000000000000000000000000000000000000000", + "base_reserve": 0, + "min_persistent_entry_ttl": 4096, + "min_temp_entry_ttl": 16, + "max_entry_ttl": 6312000, + "ledger_entries": [ + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": "ledger_key_contract_instance", + "durability": "persistent", + "val": { + "contract_instance": { + "executable": { + "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "storage": [ + { + "key": { + "string": "guide-001" + }, + "val": { + "map": [ + { + "key": { + "symbol": "author" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "category" + }, + "val": { + "vec": [ + { + "symbol": "Guide" + } + ] + } + }, + { + "key": { + "symbol": "content" + }, + "val": { + "string": "# Getting Started\\n\\nThis guide helps you..." + } + }, + { + "key": { + "symbol": "created_at" + }, + "val": { + "u64": "0" + } + }, + { + "key": { + "symbol": "helpful_count" + }, + "val": { + "u64": "0" + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "string": "guide-001" + } + }, + { + "key": { + "symbol": "language" + }, + "val": { + "string": "en" + } + }, + { + "key": { + "symbol": "tags" + }, + "val": { + "vec": [ + { + "string": "getting-started" + }, + { + "string": "beginner" + } + ] + } + }, + { + "key": { + "symbol": "title" + }, + "val": { + "string": "Getting Started with TeachLink" + } + }, + { + "key": { + "symbol": "updated_at" + }, + "val": { + "u64": "0" + } + }, + { + "key": { + "symbol": "version" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "view_count" + }, + "val": { + "u64": "0" + } + }, + { + "key": { + "symbol": "visibility" + }, + "val": { + "vec": [ + { + "symbol": "Public" + } + ] + } + } + ] + } + }, + { + "key": { + "vec": [ + { + "symbol": "ArticleCount" + } + ] + }, + "val": { + "u64": "1" + } + } + ] + } + } + } + }, + "ext": "v0" + }, + "live_until": 4095 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_code": { + "ext": "v0", + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "code": "" + } + }, + "ext": "v0" + }, + "live_until": 4095 + } + ] + }, + "events": [] +} \ No newline at end of file diff --git a/contracts/documentation/test_snapshots/test_create_tutorial.1.json b/contracts/documentation/test_snapshots/test_create_tutorial.1.json new file mode 100644 index 0000000..9119c27 --- /dev/null +++ b/contracts/documentation/test_snapshots/test_create_tutorial.1.json @@ -0,0 +1,202 @@ +{ + "generators": { + "address": 2, + "nonce": 0, + "mux_id": 0 + }, + "auth": [ + [], + [] + ], + "ledger": { + "protocol_version": 25, + "sequence_number": 0, + "timestamp": 0, + "network_id": "0000000000000000000000000000000000000000000000000000000000000000", + "base_reserve": 0, + "min_persistent_entry_ttl": 4096, + "min_temp_entry_ttl": 16, + "max_entry_ttl": 6312000, + "ledger_entries": [ + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": "ledger_key_contract_instance", + "durability": "persistent", + "val": { + "contract_instance": { + "executable": { + "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "storage": [ + { + "key": { + "string": "tutorial-001" + }, + "val": { + "map": [ + { + "key": { + "symbol": "author" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "category" + }, + "val": { + "vec": [ + { + "symbol": "Tutorial" + } + ] + } + }, + { + "key": { + "symbol": "content" + }, + "val": { + "string": "Step 1: Initialize your project..." + } + }, + { + "key": { + "symbol": "created_at" + }, + "val": { + "u64": "0" + } + }, + { + "key": { + "symbol": "helpful_count" + }, + "val": { + "u64": "0" + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "string": "tutorial-001" + } + }, + { + "key": { + "symbol": "language" + }, + "val": { + "string": "en" + } + }, + { + "key": { + "symbol": "tags" + }, + "val": { + "vec": [ + { + "string": "tutorial" + }, + { + "string": "beginner" + } + ] + } + }, + { + "key": { + "symbol": "title" + }, + "val": { + "string": "Your First Course" + } + }, + { + "key": { + "symbol": "updated_at" + }, + "val": { + "u64": "0" + } + }, + { + "key": { + "symbol": "version" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "view_count" + }, + "val": { + "u64": "0" + } + }, + { + "key": { + "symbol": "visibility" + }, + "val": { + "vec": [ + { + "symbol": "Public" + } + ] + } + } + ] + } + }, + { + "key": { + "vec": [ + { + "symbol": "ArticleCount" + } + ] + }, + "val": { + "u64": "1" + } + } + ] + } + } + } + }, + "ext": "v0" + }, + "live_until": 4095 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_code": { + "ext": "v0", + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "code": "" + } + }, + "ext": "v0" + }, + "live_until": 4095 + } + ] + }, + "events": [] +} \ No newline at end of file diff --git a/contracts/documentation/test_snapshots/test_documentation_versioning.1.json b/contracts/documentation/test_snapshots/test_documentation_versioning.1.json new file mode 100644 index 0000000..5740b76 --- /dev/null +++ b/contracts/documentation/test_snapshots/test_documentation_versioning.1.json @@ -0,0 +1,75 @@ +{ + "generators": { + "address": 1, + "nonce": 0, + "mux_id": 0 + }, + "auth": [ + [], + [], + [] + ], + "ledger": { + "protocol_version": 25, + "sequence_number": 0, + "timestamp": 0, + "network_id": "0000000000000000000000000000000000000000000000000000000000000000", + "base_reserve": 0, + "min_persistent_entry_ttl": 4096, + "min_temp_entry_ttl": 16, + "max_entry_ttl": 6312000, + "ledger_entries": [ + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": "ledger_key_contract_instance", + "durability": "persistent", + "val": { + "contract_instance": { + "executable": { + "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "storage": [ + { + "key": { + "vec": [ + { + "symbol": "Version" + } + ] + }, + "val": { + "u32": 2 + } + } + ] + } + } + } + }, + "ext": "v0" + }, + "live_until": 4095 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_code": { + "ext": "v0", + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "code": "" + } + }, + "ext": "v0" + }, + "live_until": 4095 + } + ] + }, + "events": [] +} \ No newline at end of file diff --git a/contracts/documentation/test_snapshots/test_mark_helpful.1.json b/contracts/documentation/test_snapshots/test_mark_helpful.1.json new file mode 100644 index 0000000..9bc64da --- /dev/null +++ b/contracts/documentation/test_snapshots/test_mark_helpful.1.json @@ -0,0 +1,198 @@ +{ + "generators": { + "address": 2, + "nonce": 0, + "mux_id": 0 + }, + "auth": [ + [], + [], + [], + [], + [] + ], + "ledger": { + "protocol_version": 25, + "sequence_number": 0, + "timestamp": 0, + "network_id": "0000000000000000000000000000000000000000000000000000000000000000", + "base_reserve": 0, + "min_persistent_entry_ttl": 4096, + "min_temp_entry_ttl": 16, + "max_entry_ttl": 6312000, + "ledger_entries": [ + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": "ledger_key_contract_instance", + "durability": "persistent", + "val": { + "contract_instance": { + "executable": { + "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "storage": [ + { + "key": { + "string": "helpful-test" + }, + "val": { + "map": [ + { + "key": { + "symbol": "author" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "category" + }, + "val": { + "vec": [ + { + "symbol": "Guide" + } + ] + } + }, + { + "key": { + "symbol": "content" + }, + "val": { + "string": "Very helpful content" + } + }, + { + "key": { + "symbol": "created_at" + }, + "val": { + "u64": "0" + } + }, + { + "key": { + "symbol": "helpful_count" + }, + "val": { + "u64": "2" + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "string": "helpful-test" + } + }, + { + "key": { + "symbol": "language" + }, + "val": { + "string": "en" + } + }, + { + "key": { + "symbol": "tags" + }, + "val": { + "vec": [] + } + }, + { + "key": { + "symbol": "title" + }, + "val": { + "string": "Helpful Article" + } + }, + { + "key": { + "symbol": "updated_at" + }, + "val": { + "u64": "0" + } + }, + { + "key": { + "symbol": "version" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "view_count" + }, + "val": { + "u64": "0" + } + }, + { + "key": { + "symbol": "visibility" + }, + "val": { + "vec": [ + { + "symbol": "Public" + } + ] + } + } + ] + } + }, + { + "key": { + "vec": [ + { + "symbol": "ArticleCount" + } + ] + }, + "val": { + "u64": "1" + } + } + ] + } + } + } + }, + "ext": "v0" + }, + "live_until": 4095 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_code": { + "ext": "v0", + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "code": "" + } + }, + "ext": "v0" + }, + "live_until": 4095 + } + ] + }, + "events": [] +} \ No newline at end of file diff --git a/contracts/documentation/test_snapshots/test_multilingual_content.1.json b/contracts/documentation/test_snapshots/test_multilingual_content.1.json new file mode 100644 index 0000000..1380e2e --- /dev/null +++ b/contracts/documentation/test_snapshots/test_multilingual_content.1.json @@ -0,0 +1,319 @@ +{ + "generators": { + "address": 2, + "nonce": 0, + "mux_id": 0 + }, + "auth": [ + [], + [], + [], + [], + [] + ], + "ledger": { + "protocol_version": 25, + "sequence_number": 0, + "timestamp": 0, + "network_id": "0000000000000000000000000000000000000000000000000000000000000000", + "base_reserve": 0, + "min_persistent_entry_ttl": 4096, + "min_temp_entry_ttl": 16, + "max_entry_ttl": 6312000, + "ledger_entries": [ + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": "ledger_key_contract_instance", + "durability": "persistent", + "val": { + "contract_instance": { + "executable": { + "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "storage": [ + { + "key": { + "string": "multi-en" + }, + "val": { + "map": [ + { + "key": { + "symbol": "author" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "category" + }, + "val": { + "vec": [ + { + "symbol": "Guide" + } + ] + } + }, + { + "key": { + "symbol": "content" + }, + "val": { + "string": "This is the English version..." + } + }, + { + "key": { + "symbol": "created_at" + }, + "val": { + "u64": "0" + } + }, + { + "key": { + "symbol": "helpful_count" + }, + "val": { + "u64": "0" + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "string": "multi-en" + } + }, + { + "key": { + "symbol": "language" + }, + "val": { + "string": "en" + } + }, + { + "key": { + "symbol": "tags" + }, + "val": { + "vec": [] + } + }, + { + "key": { + "symbol": "title" + }, + "val": { + "string": "Getting Started" + } + }, + { + "key": { + "symbol": "updated_at" + }, + "val": { + "u64": "0" + } + }, + { + "key": { + "symbol": "version" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "view_count" + }, + "val": { + "u64": "0" + } + }, + { + "key": { + "symbol": "visibility" + }, + "val": { + "vec": [ + { + "symbol": "Public" + } + ] + } + } + ] + } + }, + { + "key": { + "string": "multi-es" + }, + "val": { + "map": [ + { + "key": { + "symbol": "author" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "category" + }, + "val": { + "vec": [ + { + "symbol": "Guide" + } + ] + } + }, + { + "key": { + "symbol": "content" + }, + "val": { + "string": "Esta es la versi\\xc3\\xb3n en espa\\xc3\\xb1ol..." + } + }, + { + "key": { + "symbol": "created_at" + }, + "val": { + "u64": "0" + } + }, + { + "key": { + "symbol": "helpful_count" + }, + "val": { + "u64": "0" + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "string": "multi-es" + } + }, + { + "key": { + "symbol": "language" + }, + "val": { + "string": "es" + } + }, + { + "key": { + "symbol": "tags" + }, + "val": { + "vec": [] + } + }, + { + "key": { + "symbol": "title" + }, + "val": { + "string": "Primeros Pasos" + } + }, + { + "key": { + "symbol": "updated_at" + }, + "val": { + "u64": "0" + } + }, + { + "key": { + "symbol": "version" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "view_count" + }, + "val": { + "u64": "0" + } + }, + { + "key": { + "symbol": "visibility" + }, + "val": { + "vec": [ + { + "symbol": "Public" + } + ] + } + } + ] + } + }, + { + "key": { + "vec": [ + { + "symbol": "ArticleCount" + } + ] + }, + "val": { + "u64": "2" + } + } + ] + } + } + } + }, + "ext": "v0" + }, + "live_until": 4095 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_code": { + "ext": "v0", + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "code": "" + } + }, + "ext": "v0" + }, + "live_until": 4095 + } + ] + }, + "events": [] +} \ No newline at end of file diff --git a/contracts/documentation/test_snapshots/test_record_view.1.json b/contracts/documentation/test_snapshots/test_record_view.1.json new file mode 100644 index 0000000..2aef54b --- /dev/null +++ b/contracts/documentation/test_snapshots/test_record_view.1.json @@ -0,0 +1,197 @@ +{ + "generators": { + "address": 2, + "nonce": 0, + "mux_id": 0 + }, + "auth": [ + [], + [], + [], + [] + ], + "ledger": { + "protocol_version": 25, + "sequence_number": 0, + "timestamp": 0, + "network_id": "0000000000000000000000000000000000000000000000000000000000000000", + "base_reserve": 0, + "min_persistent_entry_ttl": 4096, + "min_temp_entry_ttl": 16, + "max_entry_ttl": 6312000, + "ledger_entries": [ + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": "ledger_key_contract_instance", + "durability": "persistent", + "val": { + "contract_instance": { + "executable": { + "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "storage": [ + { + "key": { + "string": "test-001" + }, + "val": { + "map": [ + { + "key": { + "symbol": "author" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "category" + }, + "val": { + "vec": [ + { + "symbol": "Guide" + } + ] + } + }, + { + "key": { + "symbol": "content" + }, + "val": { + "string": "Test content" + } + }, + { + "key": { + "symbol": "created_at" + }, + "val": { + "u64": "0" + } + }, + { + "key": { + "symbol": "helpful_count" + }, + "val": { + "u64": "0" + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "string": "test-001" + } + }, + { + "key": { + "symbol": "language" + }, + "val": { + "string": "en" + } + }, + { + "key": { + "symbol": "tags" + }, + "val": { + "vec": [] + } + }, + { + "key": { + "symbol": "title" + }, + "val": { + "string": "Test Article" + } + }, + { + "key": { + "symbol": "updated_at" + }, + "val": { + "u64": "0" + } + }, + { + "key": { + "symbol": "version" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "view_count" + }, + "val": { + "u64": "1" + } + }, + { + "key": { + "symbol": "visibility" + }, + "val": { + "vec": [ + { + "symbol": "Public" + } + ] + } + } + ] + } + }, + { + "key": { + "vec": [ + { + "symbol": "ArticleCount" + } + ] + }, + "val": { + "u64": "1" + } + } + ] + } + } + } + }, + "ext": "v0" + }, + "live_until": 4095 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_code": { + "ext": "v0", + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "code": "" + } + }, + "ext": "v0" + }, + "live_until": 4095 + } + ] + }, + "events": [] +} \ No newline at end of file diff --git a/contracts/documentation/test_snapshots/test_update_article.1.json b/contracts/documentation/test_snapshots/test_update_article.1.json new file mode 100644 index 0000000..acec88b --- /dev/null +++ b/contracts/documentation/test_snapshots/test_update_article.1.json @@ -0,0 +1,200 @@ +{ + "generators": { + "address": 2, + "nonce": 0, + "mux_id": 0 + }, + "auth": [ + [], + [], + [] + ], + "ledger": { + "protocol_version": 25, + "sequence_number": 0, + "timestamp": 0, + "network_id": "0000000000000000000000000000000000000000000000000000000000000000", + "base_reserve": 0, + "min_persistent_entry_ttl": 4096, + "min_temp_entry_ttl": 16, + "max_entry_ttl": 6312000, + "ledger_entries": [ + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": "ledger_key_contract_instance", + "durability": "persistent", + "val": { + "contract_instance": { + "executable": { + "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "storage": [ + { + "key": { + "string": "update-test" + }, + "val": { + "map": [ + { + "key": { + "symbol": "author" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "category" + }, + "val": { + "vec": [ + { + "symbol": "Guide" + } + ] + } + }, + { + "key": { + "symbol": "content" + }, + "val": { + "string": "Updated content" + } + }, + { + "key": { + "symbol": "created_at" + }, + "val": { + "u64": "0" + } + }, + { + "key": { + "symbol": "helpful_count" + }, + "val": { + "u64": "0" + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "string": "update-test" + } + }, + { + "key": { + "symbol": "language" + }, + "val": { + "string": "en" + } + }, + { + "key": { + "symbol": "tags" + }, + "val": { + "vec": [ + { + "string": "updated" + } + ] + } + }, + { + "key": { + "symbol": "title" + }, + "val": { + "string": "Updated Title" + } + }, + { + "key": { + "symbol": "updated_at" + }, + "val": { + "u64": "0" + } + }, + { + "key": { + "symbol": "version" + }, + "val": { + "u32": 2 + } + }, + { + "key": { + "symbol": "view_count" + }, + "val": { + "u64": "0" + } + }, + { + "key": { + "symbol": "visibility" + }, + "val": { + "vec": [ + { + "symbol": "Public" + } + ] + } + } + ] + } + }, + { + "key": { + "vec": [ + { + "symbol": "ArticleCount" + } + ] + }, + "val": { + "u64": "1" + } + } + ] + } + } + } + }, + "ext": "v0" + }, + "live_until": 4095 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_code": { + "ext": "v0", + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "code": "" + } + }, + "ext": "v0" + }, + "live_until": 4095 + } + ] + }, + "events": [] +} \ No newline at end of file diff --git a/contracts/documentation/tests/test_documentation.rs b/contracts/documentation/tests/test_documentation.rs new file mode 100644 index 0000000..c64674c --- /dev/null +++ b/contracts/documentation/tests/test_documentation.rs @@ -0,0 +1,317 @@ +//! Documentation Contract Test Scenarios +//! +//! Test scenarios covering various documentation types and user needs + +#![cfg(test)] + +use soroban_sdk::{testutils::Address as _, Address, Env, String, Vec}; +use teachlink_documentation::{ + DocCategory, DocumentationContract, DocumentationContractClient, Visibility, +}; + +/// Test scenario: Create a new guide article +#[test] +fn test_create_guide_article() { + let env = Env::default(); + let contract_id = env.register(DocumentationContract, ()); + env.mock_all_auths(); + + let client = DocumentationContractClient::new(&env, &contract_id); + let author = Address::generate(&env); + + // Create a guide article + let article = client.create_article( + &String::from_str(&env, "guide-001"), + &String::from_str(&env, "Getting Started with TeachLink"), + &String::from_str(&env, "# Getting Started\n\nThis guide helps you..."), + &DocCategory::Guide, + &String::from_str(&env, "en"), + &Vec::from_slice( + &env, + &[ + String::from_str(&env, "getting-started"), + String::from_str(&env, "beginner"), + ], + ), + &Visibility::Public, + &author, + ); + + assert_eq!(article.version, 1); + assert_eq!(article.view_count, 0); +} + +/// Test scenario: Create API reference documentation +#[test] +fn test_create_api_reference() { + let env = Env::default(); + let contract_id = env.register(DocumentationContract, ()); + env.mock_all_auths(); + + let client = DocumentationContractClient::new(&env, &contract_id); + let author = Address::generate(&env); + + let article = client.create_article( + &String::from_str(&env, "api-001"), + &String::from_str(&env, "Bridge API Reference"), + &String::from_str(&env, "## bridge_out\n\nLock tokens for bridging..."), + &DocCategory::ApiReference, + &String::from_str(&env, "en"), + &Vec::from_slice( + &env, + &[ + String::from_str(&env, "bridge"), + String::from_str(&env, "api"), + ], + ), + &Visibility::Public, + &author, + ); + + assert_eq!(article.category, DocCategory::ApiReference); +} + +/// Test scenario: Create tutorial content +#[test] +fn test_create_tutorial() { + let env = Env::default(); + let contract_id = env.register(DocumentationContract, ()); + env.mock_all_auths(); + + let client = DocumentationContractClient::new(&env, &contract_id); + let author = Address::generate(&env); + + let article = client.create_article( + &String::from_str(&env, "tutorial-001"), + &String::from_str(&env, "Your First Course"), + &String::from_str(&env, "Step 1: Initialize your project..."), + &DocCategory::Tutorial, + &String::from_str(&env, "en"), + &Vec::from_slice( + &env, + &[ + String::from_str(&env, "tutorial"), + String::from_str(&env, "beginner"), + ], + ), + &Visibility::Public, + &author, + ); + + assert_eq!(article.category, DocCategory::Tutorial); +} + +/// Test scenario: Create FAQ entry +#[test] +fn test_create_faq() { + let env = Env::default(); + let contract_id = env.register(DocumentationContract, ()); + env.mock_all_auths(); + + let client = DocumentationContractClient::new(&env, &contract_id); + let author = Address::generate(&env); + + let faq = client.create_faq( + &String::from_str(&env, "faq-001"), + &String::from_str(&env, "What is TeachLink?"), + &String::from_str( + &env, + "TeachLink is a decentralized knowledge-sharing platform...", + ), + &String::from_str(&env, "general"), + &String::from_str(&env, "en"), + &author, + ); + + assert_eq!(faq.helpful_count, 0); +} + +/// Test scenario: Record article view for analytics +#[test] +fn test_record_view() { + let env = Env::default(); + let contract_id = env.register(DocumentationContract, ()); + env.mock_all_auths(); + + let client = DocumentationContractClient::new(&env, &contract_id); + let author = Address::generate(&env); + + // Create article first + client.create_article( + &String::from_str(&env, "test-001"), + &String::from_str(&env, "Test Article"), + &String::from_str(&env, "Test content"), + &DocCategory::Guide, + &String::from_str(&env, "en"), + &Vec::from_slice(&env, &[]), + &Visibility::Public, + &author, + ); + + // Record view + client.record_view(&String::from_str(&env, "test-001")); + + // Get article and verify view count + let article = client.get_article(&String::from_str(&env, "test-001")); + assert_eq!(article.view_count, 1); +} + +/// Test scenario: Mark article as helpful +#[test] +fn test_mark_helpful() { + let env = Env::default(); + let contract_id = env.register(DocumentationContract, ()); + env.mock_all_auths(); + + let client = DocumentationContractClient::new(&env, &contract_id); + let author = Address::generate(&env); + + // Create article + client.create_article( + &String::from_str(&env, "helpful-test"), + &String::from_str(&env, "Helpful Article"), + &String::from_str(&env, "Very helpful content"), + &DocCategory::Guide, + &String::from_str(&env, "en"), + &Vec::from_slice(&env, &[]), + &Visibility::Public, + &author, + ); + + // Mark as helpful + client.mark_helpful(&String::from_str(&env, "helpful-test")); + client.mark_helpful(&String::from_str(&env, "helpful-test")); + + let article = client.get_article(&String::from_str(&env, "helpful-test")); + assert_eq!(article.helpful_count, 2); +} + +/// Test scenario: Update article content +#[test] +fn test_update_article() { + let env = Env::default(); + let contract_id = env.register(DocumentationContract, ()); + env.mock_all_auths(); + + let client = DocumentationContractClient::new(&env, &contract_id); + let author = Address::generate(&env); + + // Create article + client.create_article( + &String::from_str(&env, "update-test"), + &String::from_str(&env, "Original Title"), + &String::from_str(&env, "Original content"), + &DocCategory::Guide, + &String::from_str(&env, "en"), + &Vec::from_slice(&env, &[]), + &Visibility::Public, + &author, + ); + + // Update article + let updated = client.update_article( + &String::from_str(&env, "update-test"), + &String::from_str(&env, "Updated Title"), + &String::from_str(&env, "Updated content"), + &Vec::from_slice(&env, &[String::from_str(&env, "updated")]), + ); + + assert_eq!(updated.title, String::from_str(&env, "Updated Title")); + assert_eq!(updated.version, 2); +} + +/// Test scenario: Multilingual content +#[test] +fn test_multilingual_content() { + let env = Env::default(); + let contract_id = env.register(DocumentationContract, ()); + env.mock_all_auths(); + + let client = DocumentationContractClient::new(&env, &contract_id); + let author = Address::generate(&env); + + // Create English version + let _en_article = client.create_article( + &String::from_str(&env, "multi-en"), + &String::from_str(&env, "Getting Started"), + &String::from_str(&env, "This is the English version..."), + &DocCategory::Guide, + &String::from_str(&env, "en"), + &Vec::from_slice(&env, &[]), + &Visibility::Public, + &author, + ); + + // Create Spanish version + let _es_article = client.create_article( + &String::from_str(&env, "multi-es"), + &String::from_str(&env, "Primeros Pasos"), + &String::from_str(&env, "Esta es la versiΓ³n en espaΓ±ol..."), + &DocCategory::Guide, + &String::from_str(&env, "es"), + &Vec::from_slice(&env, &[]), + &Visibility::Public, + &author, + ); + + // Get versions + let en_article = client.get_article(&String::from_str(&env, "multi-en")); + let es_article = client.get_article(&String::from_str(&env, "multi-es")); + + assert_eq!(en_article.language, String::from_str(&env, "en")); + assert_eq!(es_article.language, String::from_str(&env, "es")); +} + +/// Test scenario: Documentation versioning +#[test] +fn test_documentation_versioning() { + let env = Env::default(); + let contract_id = env.register(DocumentationContract, ()); + env.mock_all_auths(); + + let client = DocumentationContractClient::new(&env, &contract_id); + + // Set version + client.update_version(&2); + + // Get version + let version = client.get_version(); + assert_eq!(version, 2); +} + +/// Test scenario: Article counts +#[test] +fn test_article_counts() { + let env = Env::default(); + let contract_id = env.register(DocumentationContract, ()); + env.mock_all_auths(); + + let client = DocumentationContractClient::new(&env, &contract_id); + let author = Address::generate(&env); + + // Create multiple articles + client.create_article( + &String::from_str(&env, "count-001"), + &String::from_str(&env, "Article 1"), + &String::from_str(&env, "Content 1"), + &DocCategory::Guide, + &String::from_str(&env, "en"), + &Vec::from_slice(&env, &[]), + &Visibility::Public, + &author, + ); + + client.create_article( + &String::from_str(&env, "count-002"), + &String::from_str(&env, "Article 2"), + &String::from_str(&env, "Content 2"), + &DocCategory::Guide, + &String::from_str(&env, "en"), + &Vec::from_slice(&env, &[]), + &Visibility::Public, + &author, + ); + + let count = client.get_article_count(); + assert_eq!(count, 2); +} diff --git a/docs/faq/README.md b/docs/faq/README.md new file mode 100644 index 0000000..e1bcffc --- /dev/null +++ b/docs/faq/README.md @@ -0,0 +1,101 @@ +# Frequently Asked Questions - TeachLink + +Common questions and answers about the TeachLink platform. + +## Table of Contents + +1. [General Questions](#general-questions) +2. [Technical Questions](#technical-questions) +3. [Development Questions](#development-questions) +4. [Governance Questions](#governance-questions) +5. [Insurance Questions](#insurance-questions) + +--- + +## General Questions + +### What is TeachLink? + +TeachLink is a decentralized knowledge-sharing platform built on the Stellar blockchain. It enables educators to tokenize their courses, students to earn rewards for learning, and provides a cross-chain bridge for global accessibility. + +### How does TeachLink work? + +TeachLink combines several key features: +- **Course Tokenization**: Educators can create tokens representing their courses +- **Reward System**: Students earn tokens for completing courses +- **Cross-Chain Bridge**: Tokens can be bridged to other blockchains +- **Insurance Protection**: Course completion insurance for students +- **Governance**: Community-driven decision making + +--- + +## Technical Questions + +### What blockchain does TeachLink use? + +TeachLink is built on the Stellar blockchain, using Soroban smart contracts. + +### What programming languages are used? + +- **Rust**: Smart contract development (Soroban) +- **JavaScript/TypeScript**: Frontend and API integrations +- **Python**: Indexer and data processing + +### How do I set up a local development environment? + +See the [Installation Guide](../knowledge-base/getting-started/installation.md) for detailed setup instructions. + +--- + +## Development Questions + +### How do I create a new course? + +1. Deploy a new course token using the TeachLink contract +2. Set up the course metadata (name, description, price) +3. Configure reward distribution +4. Optionally add insurance coverage + +### How do I integrate with the bridge? + +Use the `bridge_out` and `complete_bridge` functions in the TeachLink contract. See the [API Reference](../API_REFERENCE.md) for details. + +--- + +## Governance Questions + +### How do I participate in governance? + +Any token holder can: +1. Submit a proposal +2. Vote on active proposals +3. Execute passed proposals + +See the [Governance Documentation](../governance/README.md) for detailed instructions. + +--- + +## Insurance Questions + +### What does the insurance cover? + +The insurance protects students by providing refunds if a course is not completed due to instructor withdrawal or course cancellation. + +### How do I file a claim? + +1. Navigate to the Insurance section +2. Select the course with the issue +3. Submit a claim with documentation +4. Wait for review and approval + +--- + +## Still Have Questions? + +- Check the [Knowledge Base](../knowledge-base/README.md) +- Join our [Community Forum](https://community.teachlink.io) +- Open an [Issue](https://github.com/luhrhenz/rinacode/issues) + +--- + +*Last updated: 2026-02-22* diff --git a/docs/i18n/README.md b/docs/i18n/README.md new file mode 100644 index 0000000..42d290b --- /dev/null +++ b/docs/i18n/README.md @@ -0,0 +1,75 @@ +# Multilingual Documentation Support + +TeachLink documentation is available in multiple languages. + +## Supported Languages + +| Code | Language | Status | +|------|----------|--------| +| en | English | βœ… Primary | +| es | Spanish | 🟑 In Progress | +| fr | French | 🟑 In Progress | +| de | German | 🟑 Planned | +| zh | Chinese | 🟑 Planned | +| pt | Portuguese | 🟑 Planned | +| ja | Japanese | 🟑 Planned | + +## Language Structure + +``` +i18n/ +β”œβ”€β”€ en/ # English (default) +β”‚ └── README.md +β”œβ”€β”€ es/ # Spanish +β”‚ └── README.md +β”œβ”€β”€ fr/ # French +β”‚ └── README.md +β”œβ”€β”€ de/ # German +β”‚ └── README.md +β”œβ”€β”€ zh/ # Chinese +β”‚ └── README.md +β”œβ”€β”€ pt/ # Portuguese +β”‚ └── README.md +└── ja/ # Japanese + └── README.md +``` + +## Contributing Translations + +### Translation Process + +1. **Choose a language** from the planned list +2. **Check existing translations** in the i18n directory +3. **Create a new branch** for your translation work +4. **Translate content** following the style guide +5. **Submit a PR** for review + +### Style Guidelines + +- Use formal but accessible language +- Maintain technical terminology consistency +- Follow the original document structure +- Include code examples unchanged + +### Translation Priority + +1. README.md files +2. API Reference +3. Tutorials +4. Knowledge Base +5. FAQ + +## Language Switching + +In the web interface, use the language selector in the top navigation to switch between languages. + +For CLI tools, set the `DOC_LANG` environment variable: + +```bash +export DOC_LANG=es # Spanish +export DOC_LANG=fr # French +``` + +## Translation Status + +View the current translation progress in [TRANSLATION_STATUS.md](./TRANSLATION_STATUS.md). diff --git a/docs/knowledge-base/README.md b/docs/knowledge-base/README.md new file mode 100644 index 0000000..238f915 --- /dev/null +++ b/docs/knowledge-base/README.md @@ -0,0 +1,58 @@ +# Knowledge Base - TeachLink + +A comprehensive knowledge management system for the TeachLink platform. + +## Structure + +``` +knowledge-base/ +β”œβ”€β”€ README.md # This file +β”œβ”€β”€ getting-started/ # Getting started guides +β”‚ β”œβ”€β”€ README.md +β”‚ β”œβ”€β”€ quick-start.md +β”‚ └── installation.md +β”œβ”€β”€ concepts/ # Core concepts and architecture +β”‚ β”œβ”€β”€ README.md +β”‚ β”œβ”€β”€ blockchain-basics.md +β”‚ β”œβ”€β”€ stellar-network.md +β”‚ └── smart-contracts.md +β”œβ”€β”€ troubleshooting/ # Common issues and solutions +β”‚ β”œβ”€β”€ README.md +β”‚ └── common-issues.md +└── best-practices/ # Development best practices + β”œβ”€β”€ README.md + └── coding-standards.md +``` + +## Categories + +### Getting Started +- Quick start guides for new developers +- Installation and setup instructions +- Your first TeachLink application + +### Concepts +- Understanding blockchain fundamentals +- Stellar network architecture +- Smart contract development concepts + +### Troubleshooting +- Common errors and their solutions +- Debugging guides +- FAQ for development issues + +### Best Practices +- Coding standards and conventions +- Security best practices +- Performance optimization + +## Contributing + +To contribute to the knowledge base: +1. Create a new markdown file in the appropriate category +2. Follow the documentation style guide +3. Submit a pull request + +## Search + +Use the documentation search to find specific topics across all knowledge base articles. diff --git a/docs/search.json b/docs/search.json new file mode 100644 index 0000000..394dd84 --- /dev/null +++ b/docs/search.json @@ -0,0 +1,41 @@ +{ + "search": { + "enabled": true, + "indexPath": ".search-index", + "fields": [ + { + "name": "title", + "weight": 10 + }, + { + "name": "content", + "weight": 5 + }, + { + "name": "tags", + "weight": 8 + }, + { + "name": "category", + "weight": 3 + } + ], + "tokenizer": "standard", + "stopWords": ["the", "a", "an", "and", "or", "but", "in", "on", "at", "to", "for"], + "fuzzySearch": true, + "fuzzyThreshold": 0.8 + }, + "discovery": { + "relatedContent": true, + "trendingTopics": true, + "popularArticles": true, + "recentUpdates": true + }, + "analytics": { + "trackViews": true, + "trackSearchQueries": true, + "trackHelpfulVotes": true, + "trackTimeOnPage": true, + "anonymizeIp": true + } +} diff --git a/docs/tutorials/README.md b/docs/tutorials/README.md new file mode 100644 index 0000000..4b74d57 --- /dev/null +++ b/docs/tutorials/README.md @@ -0,0 +1,76 @@ +# Tutorials - TeachLink + +Step-by-step tutorials for building on the TeachLink platform. + +## Available Tutorials + +### Beginner Tutorials + +1. **[Your First Course](./beginner/01-first-course.md)** + - Create your first tokenized course + - Set up basic metadata + - Deploy to testnet + +2. **[Understanding Tokens](./beginner/02-understanding-tokens.md)** + - Learn about tokenization + - Create custom tokens + - Manage token supply + +3. **[Basic Rewards System](./beginner/03-rewards-system.md)** + - Set up reward distribution + - Configure completion milestones + - Track student progress + +### Intermediate Tutorials + +4. **[Cross-Chain Bridging](./intermediate/01-bridging.md)** + - Bridge tokens to other chains + - Configure validators + - Handle bridge transactions + +5. **[Insurance Integration](./intermediate/02-insurance.md)** + - Add insurance to courses + - Configure premiums + - Handle claims + +6. **[Building a Frontend](./intermediate/03-frontend.md)** + - Connect to Stellar wallet + - Interact with contracts + - Display course data + +### Advanced Tutorials + +7. **[Custom Governance](./advanced/01-governance.md)** + - Create governance proposals + - Implement voting mechanisms + - Execute passed proposals + +8. **[Advanced Contract Patterns](./advanced/02-contract-patterns.md)** + - Upgradeable contracts + - Multi-sig authorization + - Batch operations + +## Interactive Tutorials + +For hands-on learning, try our [Interactive Documentation](../interactive/README.md): + +```bash +cd docs/interactive +cargo run +``` + +Then open http://localhost:3000 in your browser. + +## Prerequisites + +Before starting tutorials, ensure you have: +- Rust toolchain installed +- Stellar CLI (stellar) installed +- A Stellar wallet (Freighter or Albedo) +- Testnet XLM tokens for testing + +## Next Steps + +- [Quick Start Guide](../knowledge-base/getting-started/quick-start.md) +- [API Reference](../API_REFERENCE.md) +- [Knowledge Base](../knowledge-base/README.md) diff --git a/docs/tutorials/beginner/01-first-course.md b/docs/tutorials/beginner/01-first-course.md new file mode 100644 index 0000000..72d92e2 --- /dev/null +++ b/docs/tutorials/beginner/01-first-course.md @@ -0,0 +1,88 @@ +# Tutorial: Your First Course + +In this tutorial, you'll learn how to create your first tokenized course on TeachLink. + +## Prerequisites + +- Rust toolchain installed +- Stellar CLI installed +- Testnet wallet with XLM + +## Step 1: Initialize Your Project + +Create a new directory for your course: + +```bash +mkdir my-first-course +cd my-first-course +``` + +## Step 2: Create the Course Contract + +Create a new Rust project: + +```bash +cargo new --lib course +cd course +``` + +## Step 3: Define Course Metadata + +Add the following to your `src/lib.rs`: + +```rust +use soroban_sdk::{Address, Env}; + +pub struct Course { + pub name: String, + pub description: String, + pub price: i128, + pub instructor: Address, +} + +impl Course { + pub fn new(name: String, description: String, price: i128, instructor: Address) -> Self { + Self { + name, + description, + price, + instructor, + } + } +} +``` + +## Step 4: Build and Deploy + +Build your contract: + +```bash +cargo build --target wasm32-unknown-unknown --release +``` + +Deploy to testnet: + +```bash +stellar contract deploy \ + --wasm target/wasm32-unknown-unknown/release/course.wasm \ + --source your-wallet-key \ + --network testnet +``` + +## Step 5: Initialize the Course + +```rust +use course::Course; + +let course = Course::new( + "Introduction to Blockchain".to_string(), + "Learn the fundamentals of blockchain technology".to_string(), + 1000, // Price in XLM (with 7 decimals) + instructor_address, +); +``` + +## Next Steps + +- [Understanding Tokens](../beginner/02-understanding-tokens.md) +- [Basic Rewards System](../beginner/03-rewards-system.md) diff --git a/docs/versions/README.md b/docs/versions/README.md new file mode 100644 index 0000000..462e2ec --- /dev/null +++ b/docs/versions/README.md @@ -0,0 +1,88 @@ +# Documentation Versioning + +TeachLink documentation supports multiple versions to maintain backward compatibility and provide access to historical documentation. + +## Version Structure + +``` +versions/ +β”œβ”€β”€ README.md # This file +β”œβ”€β”€ current/ # Current version (symlink or copy) +β”œβ”€β”€ v1.0/ # Version 1.0 +β”‚ β”œβ”€β”€ README.md +β”‚ └── ... +β”œβ”€β”€ v0.9/ # Version 0.9 (legacy) +β”‚ └── ... +└── CHANGELOG.md # Version history +``` + +## Versioning Strategy + +### Version Numbering + +- **Major versions** (v1.0, v2.0): Significant API changes, new features +- **Minor versions** (v1.1, v1.2): New features, backward compatible +- **Patch versions** (v1.0.1): Bug fixes, documentation updates + +### Version Lifecycle + +| Status | Description | +|--------|-------------| +| Current | Active development, latest features | +| Supported | Bug fixes, security updates | +| Legacy | Security fixes only | +| Deprecated | No longer maintained | + +## Current Versions + +| Version | Status | Released | Support Until | +|---------|--------|----------|---------------| +| v1.0 | Current | 2026-02-01 | - | +| v0.9 | Legacy | 2025-08-15 | 2026-08-15 | + +## Switching Versions + +### Web Interface + +Use the version selector in the top navigation bar. + +### CLI + +```bash +# View specific version +export DOC_VERSION=v1.0 + +# Use latest +export DOC_VERSION=latest +``` + +### URL Structure + +``` +https://docs.teachlink.io/en/v1.0/ +https://docs.teachlink.io/en/v0.9/ +``` + +## Contributing Updates + +### Updating Documentation + +1. Create a branch for your changes +2. Make edits to files in `docs/` +3. Submit a PR with changes + +### Adding New Version + +When a major version is released: +1. Copy current docs to new version directory +2. Update version references +3. Create version switcher entry +4. Update CHANGELOG.md + +## Changelog + +See [CHANGELOG.md](./CHANGELOG.md) for detailed version history. + +--- + +*For API versioning, see [API_REFERENCE.md](../API_REFERENCE.md)*