From 3c0649918d4fa05fd9b6131dd6cd8741e2818b6f Mon Sep 17 00:00:00 2001 From: "yongen.loong" Date: Thu, 29 Aug 2024 16:53:42 +0800 Subject: [PATCH] feat: todo tutorial --- app/api/get-tutorial-list/route.ts | 10 + app/tutorials/[id]/_content/todo/todo.mdx | 268 ++++++++++++++++++++++ components/tutorial/generate-template.tsx | 18 +- 3 files changed, 295 insertions(+), 1 deletion(-) create mode 100644 app/tutorials/[id]/_content/todo/todo.mdx diff --git a/app/api/get-tutorial-list/route.ts b/app/api/get-tutorial-list/route.ts index b4f0f71..5fb1f9d 100644 --- a/app/api/get-tutorial-list/route.ts +++ b/app/api/get-tutorial-list/route.ts @@ -46,6 +46,16 @@ export async function GET() { lang: "C#", langId: "csharp", }, + { + id: "todo", + img: "/todo.jpeg", + title: "ToDo Contract", + description: "A basic ToDo smart contract", + level: "Intermediate", + levelId: "intermediate", + lang: "C#", + langId: "csharp", + }, ].filter((i) => (solidityEnabled ? true : i.langId !== "solidity")); return Response.json(data); diff --git a/app/tutorials/[id]/_content/todo/todo.mdx b/app/tutorials/[id]/_content/todo/todo.mdx new file mode 100644 index 0000000..25d7f94 --- /dev/null +++ b/app/tutorials/[id]/_content/todo/todo.mdx @@ -0,0 +1,268 @@ +# ToDo + +**Description**: This contract is moderately complex, demonstrating task management functionalities such as creating, updating, and deleting tasks. It also includes features for task prioritization and user interaction. + +**Purpose**: To introduce you to task management systems in smart contracts, focusing on state management, user interactions, and contract updates for efficient handling of to-do tasks. + +**Difficulty Level**: Moderate + +## Step 1 - Develop Smart Contract + +### Adding Your Smart Contract Code + +First, generate the template: . + +Now that we have a template todo list project, we can customise the template to incorporate our own contract logic. +Let's start by implementing methods to handle the basic functionality of creating, editing, listing, deleting, and marking tasks as complete within the contract state. ToDo dApp includes the below functionalities like: +1. Create a task (Name, category, description, createAt, updatedAt) +2. Mark task as completed +3. Delete a task +4. List all the tasks +5. Edit a task + +#### Defining Methods and Messages + +The implementation of `todo_app.proto` file inside folder `src/Protobuf/contract/` is as follows: + +```csharp title="todo_app.proto" +syntax = "proto3"; +import "aelf/options.proto"; +import "google/protobuf/empty.proto"; +import "google/protobuf/wrappers.proto"; +import "Protobuf/reference/acs12.proto"; +// The namespace of this class +option csharp_namespace = "AElf.Contracts.ToDo"; +service ToDo { + // The name of the state class the smart contract is going to use to access blockchain state + option (aelf.csharp_state) = "AElf.Contracts.ToDo.ToDoState"; + option (aelf.base) = "Protobuf/reference/acs12.proto"; + rpc Initialize (google.protobuf.Empty) returns (google.protobuf.Empty) { + } + rpc CreateTask (TaskInput) returns (google.protobuf.StringValue) { + } + rpc UpdateTask (TaskUpdateInput) returns (google.protobuf.Empty) { + } + rpc DeleteTask (google.protobuf.StringValue) returns (google.protobuf.Empty) { + } + rpc ListTasks (google.protobuf.StringValue) returns (TaskList) { + option (aelf.is_view) = true; + } + rpc GetTask (google.protobuf.StringValue) returns (Task) { + option (aelf.is_view) = true; + } + rpc GetInitialStatus (google.protobuf.Empty) returns (google.protobuf.BoolValue) { + option (aelf.is_view) = true; + } +} +// A message to represent a task +message Task { + string task_id = 1; + string name = 2; + string description = 3; + string category = 4; + string status = 5; + string owner = 6; + int64 created_at = 7; + int64 updated_at = 8; +} +// Input for creating a task +message TaskInput { + string name = 1; + string description = 2; + string category = 3; +} +// Input for updating a task +message TaskUpdateInput { + string task_id = 1; + string name = 2; + string description = 3; + string category = 4; + string status = 5; +} +// List of tasks +message TaskList { + repeated Task tasks = 1; +} +``` + +- `rpc` methods define the callable functions within the contract, allowing external systems to interact with the contract's logic. +- `message` represent the structured data exchanged between the contract and external systems. + +#### Define Contract States + +The implementation of the ToDo app state inside file `src/ToDoAppState.cs` is as follows: + +```csharp title="src/ToDoAppState.cs" +using AElf.Sdk.CSharp.State; +using AElf.Types; + +namespace AElf.Contracts.ToDo +{ + public class ToDoState : ContractState + { + public BoolState Initialized { get; set; } + public SingletonState
Owner { get; set; } + public MappedState Tasks { get; set; } // Mapping of task ID to Task + public MappedState TaskExistence { get; set; } // Mapping to track task existence + public StringState TaskIds { get; set; } // Concatenated string of task IDs + public Int32State TaskCounter { get; set; } // Counter for generating unique IDs + } +} +``` + +- The `State.cs` file in an aelf blockchain smart contract holds the variables that store the contract's data, making sure this data is saved and accessible whenever the contract needs it. + +#### Implement ToDo Smart Contract + +The implementation of the ToDo App smart contract inside file `src/ToDoApp.cs` is as follows: + +```csharp title="src/ToDoApp.cs" +using Google.Protobuf.WellKnownTypes; +using System.Collections.Generic; +namespace AElf.Contracts.ToDo +{ + public class ToDo : ToDoContainer.ToDoBase + { + public override Empty Initialize(Empty input) + { + if (State.Initialized.Value) + { + return new Empty(); + } + State.Initialized.Value = true; + State.Owner.Value = Context.Sender; + State.TaskIds.Value = ""; + State.TaskCounter.Value = 0; + return new Empty(); + } + public override StringValue CreateTask(TaskInput input) + { + if (!State.Initialized.Value) + { + return new StringValue { Value = "Contract not initialized." }; + } + var taskId = (State.TaskCounter.Value + 1).ToString(); + State.TaskCounter.Value++; + var timestamp = Context.CurrentBlockTime.Seconds; + // Create task dictionary entry directly in ToDo class + State.Tasks[taskId] = new Task + { + TaskId = taskId, + Name = input.Name, + Description = input.Description, + Category = input.Category, + Status = "pending", + CreatedAt = timestamp, + UpdatedAt = timestamp, + Owner = Context.Sender.ToString().Trim('"'), + }; + State.TaskExistence[taskId] = true; + // Append task ID to the list of IDs + var existingTaskIds = State.TaskIds.Value; + existingTaskIds += string.IsNullOrEmpty(existingTaskIds) ? taskId : $",{taskId}"; + State.TaskIds.Value = existingTaskIds; + return new StringValue { Value = taskId }; + } + public override Empty UpdateTask(TaskUpdateInput input) + { + var task = State.Tasks[input.TaskId]; + if (task == null) + { + return new Empty(); // Handle case if task doesn't exist + } + task.Name = input.Name ?? task.Name; + task.Description = input.Description ?? task.Description; + task.Category = input.Category ?? task.Category; + task.Status = input.Status ?? task.Status; + task.UpdatedAt = Context.CurrentBlockTime.Seconds; + State.Tasks[input.TaskId] = task; + return new Empty(); + } + public override Empty DeleteTask(StringValue input) + { + State.Tasks.Remove(input.Value); + State.TaskExistence.Remove(input.Value); + // Remove task ID from the list of IDs + var existingTaskIds = State.TaskIds.Value.Split(','); + var newTaskIds = new List(existingTaskIds.Length); + foreach (var taskId in existingTaskIds) + { + if (taskId != input.Value) + { + newTaskIds.Add(taskId); + } + } + State.TaskIds.Value = string.Join(",", newTaskIds); + return new Empty(); + } + public override TaskList ListTasks(StringValue input) + { + var owner = input.Value; // Get the owner value from the input + var taskList = new TaskList(); + var taskIds = State.TaskIds.Value.Split(','); + foreach (var taskId in taskIds) + { + var task = State.Tasks[taskId]; + if (task != null && task.Owner == owner) // Filter tasks by owner + { + taskList.Tasks.Add(task); + } + } + return taskList; + } + public override Task GetTask(StringValue input) + { + var task = State.Tasks[input.Value]; + if (task == null) + { + return new Task { TaskId = input.Value, Name = "Task not found." }; + } + return task; + } + public override BoolValue GetInitialStatus(Empty input) + { + return new BoolValue { Value = State.Initialized.Value }; + } + } +} +``` + +## Step 2 - Building Smart Contract + +Build the new code by clicking the Build button. + +## Step 3 - Deploy Smart Contract + +Deploy the new code by clicking the Deploy button. + +## 🎯 Conclusion + +🎉 Congratulations on successfully completing the **ToDo dApp** tutorial! 🎉 You've taken important steps in setting up your development environment, developing and deploying a smart contract on ToDo dApp, and building a fully functional ToDo decentralized application on the aelf blockchain. 🌟 + +**📚 What You've Learned** + +Throughout this tutorial, you've mastered: + + - **💻 Developing Your Smart Contract:** You created the foundation of your ToDo dApp by writing and building the smart contract that manages tasks, from creation to deletion. + + - **🚀 Deploying the Smart Contract:** You deployed your smart contract to the aelf blockchain, enabling its functionalities to be used in a live environment. + +**🔍 Final Output** + +By now, you should have: + + - 📜 A deployed smart contract that powers your ToDo dApp, managing tasks with functionalities for creation, updating, status management, and deletion. + +**➡️ What's Next?** + +With the basics under your belt, consider exploring more advanced topics: + + - **📈 Enhancing Smart Contract Logic:** Introduce more complex features to your ToDo dApp, such as prioritization, deadlines, or collaboration tools. + + - **🔒 Improving Security:** Ensure your dApp and smart contract are secure by implementing best practices and security measures. + + - **🌍 Exploring Cross-Chain Features:** Expand your dApp's capabilities by exploring aelf's cross-chain interoperability, enabling interaction with other blockchains. + +The possibilities with blockchain technology and decentralized applications are endless. You're now well-equipped to take your ToDo dApp to the next level. Keep building, innovating, and exploring with aelf. 🚀 + +Happy coding and expanding your **ToDo dApp! 😊** diff --git a/components/tutorial/generate-template.tsx b/components/tutorial/generate-template.tsx index ceec6b6..f203095 100644 --- a/components/tutorial/generate-template.tsx +++ b/components/tutorial/generate-template.tsx @@ -8,9 +8,11 @@ import { mutate } from "swr"; export default function GenerateTemplate({ name = "HelloWorld", template = "aelf", + renameHelloWorldProto = "hello_world_contract", }: { name?: string; template?: string; + renameHelloWorldProto?: string; }) { const pathname = usePathname(); @@ -29,9 +31,23 @@ export default function GenerateTemplate({ const res = await fetch( `/api/get-template-data?id=${template}&name=${name}` ); - const templateData: { path: string; contents: string }[] = + const _templateData: { path: string; contents: string }[] = await res.json(); + const templateData = _templateData.map((i) => { + if (i.path.includes("hello_world_contract.proto")) { + return { + ...i, + path: i.path.replace( + "hello_world_contract.proto", + `${renameHelloWorldProto}.proto` + ), + }; + } + + return i; + }); + await db.files.bulkDelete( (await db.files.toArray()) .map((i) => i.path)