Skip to content

Commit

Permalink
feat: todo tutorial
Browse files Browse the repository at this point in the history
  • Loading branch information
yongenaelf committed Aug 29, 2024
1 parent cb2ffda commit 3c06499
Show file tree
Hide file tree
Showing 3 changed files with 295 additions and 1 deletion.
10 changes: 10 additions & 0 deletions app/api/get-tutorial-list/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
268 changes: 268 additions & 0 deletions app/tutorials/[id]/_content/todo/todo.mdx
Original file line number Diff line number Diff line change
@@ -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: <GenerateTemplate name="ToDoApp" renameHelloWorldProto="todo_app" />.

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<Address> Owner { get; set; }
public MappedState<string, Task> Tasks { get; set; } // Mapping of task ID to Task
public MappedState<string, bool> 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<string>(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! 😊**
18 changes: 17 additions & 1 deletion components/tutorial/generate-template.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand All @@ -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)
Expand Down

0 comments on commit 3c06499

Please sign in to comment.