CRUD dApp con NEAR y VUE Capítulo 1

Phoenix Pulsar
7 min readMay 27, 2022

En este tutorial vamos a construir un CRUD dApp con NEAR Protocol, una tecnología de cadena en bloques (Blockchain). En el capítulo dos construiremos la aplicación web usando VUE.

Código Fuente : https://github.com/phoenixpulsar/vehicles-on-the-block-smart-contract

El tutorial esta dividido en una serie de pasos. En cualquier momento se puede revisar el estado del código en un paso particular usando Git.

Historial Git

Antes de comenzar - Disclaimer:
El objetivo de este tutorial es para fines educativos. El código que se presenta no es código terminado o listo para producción. Se presentará solo una de las muchas formas en la que se puede construir una aplicación descentralizada usando NEAR. Por último, retroalimentación es bienvenida con el objetivo de crear un tutorial que sea útil para la comunidad.

Requisitos:
Tener experiencia programando y saber usar software de fuente abierta (open source)

near-cli
npm
git
Github

Demo:
https://recordit.co/1RQAM9kmOp

Vamos a construir una aplicación web que permita a un usuario LEER (READ), CREAR(CREATE), EDITAR(UPDATE) y BORRAR(DELETE) vehículos y servicios de dichos vehículos y almacenarlos en la cadena de bloques de NEAR.

— — — — — — — — — — — — — — — —
Paso 1. Andamios
Vamos a usar el proyecto que esta estructurado con lo mínimo necesario para empezar a construir nuestro contrato inteligente. Ir al siguiente link y hacer click en USE TEMPLATE: https://github.com/Learn-NEAR/starter--near-sdk-as

— — — — — — — — — — — — — — — —
Paso 2. Primeras modificaciones
1. Borrar el folder starter--near-sdk-as/src/simple

2. Cambiar el nombre de starter--near-sdk-as/src/singleton to starter--near-sdk-as/src/vehicleGarage

3. Navegar astarter--near-sdk-as/asconfig.json y cambiar el contenido de este archivo a:

{
"workspaces": ["src/vehicleGarage"]
}

4. Desde la terminal, correr el comandoyarn o npm i

Estas modificaciones nos pone en un buen lugar para empezar a configurar nuestro contrato inteligente.

— — — — — — — — — — — — — — — —

Paso 3. Modelos
1. Crea el archivo models.ts en src/vehicleGarage/assembly
2. Importamos denear-sdk-as lo siguiente:

import {
context,
storage,
PersistentMap,
PersistentSet
} from "near-sdk-as";

Estructura:

@nearBindgen
export class Vehicle {}
@nearBindgen
export class VehicleService{}
@nearBindgen
export class VehicleGarage {}

3. Modificarsrc/utils.ts y agregar lo siguiente

export const GARAGE_KEY = "state";
export type VehicleId = string;
export type VehicleServiceId = string;
/**
* @function idCreator
* @return {string}
* Creates a unique id by combining sender + block height
*/
export function idCreator(): string {
let id = Context.blockIndex.toString();
return id;
}

TIP: Usar el blockIndex para crear un numero de identificación no es ideal, pero por simplicidad es lo que usaremos. Una mejor forma de crear un numero de identificación seria:

/**
* @function idCreator
* @return {string}
* Creates a unique id by combining sender + block height
*/
export function idCreator(): string {
let title = Context.sender
let id = title + '-' + Context.blockIndex.toString()
return id
}

Agregar lo que importamos en nuestro model.ts y también vamos a agregar un par de funciones y variables que estaban incluidas en el código que usamos en paso 1 (USE TEMPLATE)

import { 
VehicleId,
VehicleServiceId,
AccountId,
Timestamp,
idCreator,
GARAGE_KEY
} from "../../utils";

4. Usaremos dos colecciones que nos ayudaran a interactuar con el almacenamiento(Storage) en la cadena de bloques.

const vehicles = new PersistentMap<VehicleId, Vehicle>("v");
const vehicleService = new PersistentMap<VehicleServiceId, VehicleService>("vs");

PersitentMap es similar a un Objeto en JavaScript que nos permite guardar llaves y valores. La (“v”) y (“vs”) now ayuda a prevenir colisiones de datos. Más información aquí

5. Llegó el momento de crear el plano de nuestro vehículo y servicios de vehículos creando un para de Clases.

Vehículo

@nearBindgen
export class Vehicle {
public id: VehicleId = idCreator();
created_at: Timestamp = context.blockTimestamp;
public serviceIds: PersistentSet<VehicleServiceId> = new PersistentSet<VehicleServiceId>("s");


constructor(
public year: string,
public make: string,
public model: string,
public owner: AccountId,
public vehicleNotes: string,
public dateAcquired: string
) {}
}

Servicio

@nearBindgen
export class VehicleService {
public id: VehicleServiceId = idCreator();
created_at: Timestamp = context.blockTimestamp;

constructor(
public vehicleId: VehicleId,
public serviceDate: string,
public serviceNotes: string
) {}
}

Estamos usando PersistentSet porque sabemos que un vehículo no tendrá dos servicios con el mismo id(número de identificación), esto nos ayudará cuando queramos agregar, editar o borrar un servicio para un vehículo.

Podrán notar que los clases de arriba son bastante sencillas, las usamos como un plano para crear objetos con información que queremos guardar. Las podríamos extender para guardar o tener otras funcionalidades.

6. En este paso vamos a construir la “cochera” VehicleGarage. En esta clase es donde vamos a tener mayoría de nuestro lógica de LEER, EDITAR, CREAR y BORRAR.

Vehicle Garage

Crear VehicleGarage

@nearBindgen
export class VehicleGarage {
creator: AccountId = context.predecessor;
created_at: Timestamp = context.blockTimestamp;

static create_garage(): void {
const garage = new VehicleGarage();
this.set_garage(garage);
}

static get_garage(): VehicleGarage {
return storage.getSome<VehicleGarage>(GARAGE_KEY);
}

static set_garage(garage: VehicleGarage): void {
storage.set(GARAGE_KEY, garage);
}
// -------------------------------------------
// Vehicle Methods
// -------------------------------------------
// -------------------------------------------
// Service Methods
// -------------------------------------------
}

Métodos del Vehículo:

Agregar un Vehículo

static add_vehicle(
year: string,
make: string,
model: string,
owner: AccountId,
vehicleNotes: string,
dateAcquired: string
): void {
let newVehicle = new Vehicle(
year,
make,
model,
owner,
dateAcquired,
vehicleNotes
);
vehicles.set(newVehicle.id, newVehicle);
}

Editar un Vehículo

static update_vehicle(
vehicleId: VehicleId,
year: string,
make: string,
model: string,
owner: AccountId,
vehicleNotes: string,
dateAcquired: string
): void {
let currentVehicle = vehicles.get(vehicleId);
if (currentVehicle !== null) {
currentVehicle.year = year;
currentVehicle.make = make;
currentVehicle.model = model;
currentVehicle.owner = owner;
currentVehicle.vehicleNotes = vehicleNotes;
currentVehicle.dateAcquired = dateAcquired;
vehicles.set(vehicleId, currentVehicle);
}
}

Borrar un vehículo

static delete_service_id_from_vehicle(
vehicleId: VehicleId,
vehicleServiceId: VehicleServiceId
): void {
let currentVehicle = vehicles.get(vehicleId);
if (currentVehicle !== null) {
let serviceIds = currentVehicle.serviceIds;
serviceIds.delete(vehicleServiceId);
vehicles.set(vehicleId, currentVehicle);
}
}

static delete_vehicle(vehicleId: VehicleId): void {
// grab all service ids
let currentVehicle = vehicles.get(vehicleId);

if (currentVehicle !== null) {
let vehicleServiceIds = currentVehicle.serviceIds.values();
// delete all services from vehicle
if (vehicleServiceIds.length) {
for (let i = 0; i < vehicleServiceIds.length; ++i) {
VehicleGarage.delete_vehicle_service(vehicleServiceIds[i]);
}
}
}

// delete vehicle
vehicles.delete(vehicleId);
}

Para borrar un vehículo necesitamos agregar un par de pasos más. Cuando borramos un vehículo, también queremos borrar cualquier servicio asociado de ese vehículo. También pueden ver delete_service_id_from_vehicle, esto nos ayudará cuando queramos borrar un servicio.

Métodos de Servicio

Agregar servicio

static add_vehicle_service(
vehicleId: VehicleId,
serviceDate: string,
serviceNotes: string
): void {
let newVehicleService = new VehicleService(
vehicleId,
serviceDate,
serviceNotes
);
vehicleService.set(newVehicleService.id, newVehicleService);
VehicleGarage.add_service_id(vehicleId, newVehicleService.id);
}
static add_service_id(
vehicleId: VehicleId,
vehicleServiceId: VehicleServiceId
): void {
let currentVehicle = vehicles.get(vehicleId);
if (currentVehicle !== null) {
currentVehicle.serviceIds.add(vehicleServiceId);
vehicles.set(vehicleId, currentVehicle);
}
}

Editar Servicio

static update_vehicle_service(
vehicleServiceId: VehicleServiceId,
vehicleId: VehicleId,
serviceDate: string,
serviceNotes: string
): void {
let currentVehicleService = vehicleService.get(vehicleServiceId);
if (currentVehicleService !== null) {
currentVehicleService.vehicleId = vehicleId;
currentVehicleService.serviceDate = serviceDate;
currentVehicleService.serviceNotes = serviceNotes;
vehicleService.set(vehicleServiceId, currentVehicleService);
}
}

Borrar Servicio

static delete_vehicle_service(vehicleServiceId: VehicleServiceId): void {
let currentVehicleService = vehicleService.get(vehicleServiceId);
if (currentVehicleService !== null) {
let currentVehicleId = currentVehicleService.vehicleId;
VehicleGarage.delete_service_id_from_vehicle(
currentVehicleId,
vehicleServiceId
);
}
vehicleService.delete(vehicleServiceId);
}
}

Cuando borramos un servicio, nos aseguramos que también borremos el servicio en el vehículo.

— — — — — — — — — — — — — — — —

Paso 4. Contrato Inteligente
En este paso vamos a crear las funciones que nos permitirán interactuar con nuestro contrato desde la terminal y eventualmente nuestra aplicación web.

Navigate to src/vehicleGarage/assembly/index.ts

1. Borra todo el contenido del archivo src/vehicleGarage/assembly/index.ts y agrega las siguientes funciones.

export function init():void{}
export function get_vehicle_garage():VehicleGarage{}
export function add_vehicle():void{}
export function update_vehicle():void{}
export function delete_vehicle():void{}
export function add_vehicle_service():void{}
export function update_vehicle_service():void{}
export function delete_vehicle_service():void{}

2. Importamos modelos y funciones de utils ymodels

import {storage} from "near-sdk-as";
import {
VehicleId,
VehicleServiceId,
AccountId,
GARAGE_KEY
} from "../../utils";
import { VehicleGarage } from "./models";

3. Conectamos nuestras funciones con nuestros models

export function init(): void {
VehicleGarage.create_garage();
} export function get_vehicle_garage(): VehicleGarage {
return VehicleGarage.get_garage();
} export function add_vehicle(
year: string,
make: string,
model: string,
owner: AccountId,
vehicleNotes: string,
dateAcquired: string
): void {
VehicleGarage.add_vehicle(
year,
make,
model,
owner,
vehicleNotes,
dateAcquired
);
} export function update_vehicle(
vehicleId: VehicleId,
year: string,
make: string,
model: string,
owner: AccountId,
vehicleNotes: string,
dateAcquired: string
): void {
VehicleGarage.update_vehicle(
vehicleId,
year,
make,
model,
owner,
vehicleNotes,
dateAcquired
);
} export function delete_vehicle(vehicleId: VehicleId): void {
VehicleGarage.delete_vehicle(vehicleId);
} export function add_vehicle_service(
vehicleId: VehicleId,
serviceDate: string,
serviceNotes: string
): void {
VehicleGarage.add_vehicle_service(vehicleId, serviceDate, serviceNotes);
} export function update_vehicle_service(
vehicleServiceId: VehicleServiceId,
vehicleId: VehicleId,
serviceDate: string,
serviceNotes: string
): void {
VehicleGarage.update_vehicle_service(
vehicleServiceId,
vehicleId,
serviceDate,
serviceNotes
);
} export function delete_vehicle_service(
vehicleServiceId: VehicleServiceId
): void {
VehicleGarage.delete_vehicle_service(vehicleServiceId);
}

4. En estas funciones, podemos agregar lógica para afirmar que las funciones corran solo si se cumplen ciertas reglas. Por ejemplo, podemos revisar que la persona que esta firmando(signer) es el dueño del contrato, si el contrato ya fue inicializado, etc…

function is_initialized(): bool {
return storage.hasKey(GARAGE_KEY);
}function assert_contract_is_initialized(): void {
assert(is_initialized(), "Contract must be initialized first.");
}

— — — — — — — — — — — — — — — —

Paso 5. Subir el contrato a Testnet

  1. Desde el folder raiz, (donde esta package.json)
yarn build:release

Verifica que en build/release creó el archivo vehicleGarage.wasm

2. Una vez que tengas una cuenta en testnet, crear una sub-cuenta dónde subiremos nuestro contrato inteligente. Desde la terminal asegúrate que estas conectado near login antes de crear la sub-cuenta

near create-account <sub.account.testnet> --masterAccount <account.testnet>

Una forma de hacerlo sería:

near create-account vehicleGarage1.<your_testnet_account>.testnet --masterAccount <your_testnet_account>.testnet

3. Creamos unas variables desde nuestra terminal que hará más fácil la interacción con nuestro contrato

export WASM_FILE=./build/release/vehicleGarage.wasm
export CONTRACT_ACCOUNT=<sub_acc_created_prev_step>

4. Subir el contrato a testnet

near deploy $CONTRACT_ACCOUNT $WASM_FILE --initFunction init --initArgs '{}' --account_id $CONTRACT_ACCOUNT

— — — — — — — — — — — — — — — —

Paso 6. Interactuar con nuestro contrato

  1. Copia (‘Clone’) el siguiente proyecto. Nos ayudará a interactuar y revisar el estado de nuestro contrato
    https://github.com/near-examples/near-account-utils
  2. Probemos nuestro contrato
near view $CONTRACT_ACCOUNT get_vehicle_garage

Output:

{
creator: 'sub.acc.testnet',
created_at: '1647995834696720123'
}

creator and created_at será diferente para ti dependiendo las variables que usaste para $CONTRACT_ACCOUNT

Ahora usaremosnear-account-utils
Corre el comando desde near-acount-utils y asegúrate que ves lo mismo que en el paso anterior

yarn storage <sub.acc.testnet>

3. Interactuar con las otras funciones

near call $CONTRACT_ACCOUNT add_vehicle '{"year":"2015", "make": "mini", "model":"countryman", "owner": "someaccount.testnet", "vehicleNotes": "new", "dateAcquired":"today" }' --account_id <signer_account>

Eso es todo. Dejó como ejercicio al lector probar todas las funciones que creamos. Que podamos agregar un vehículo, editarlo, borrarlo, agregar un servicio, editarlo, borrarlo. En parte 2 de este tutorial veremos como crear un aplicación para que diferentes usuarios puedan interactuar con el contrato.

--

--

Phoenix Pulsar
0 Followers

“Think deeply about things, don’t just ago along because that is the way things are” — Aaron Swartz