Hello Hive-, Java-, Node.js-, Angular-, Docker-freaks,
Hello all others,
In the long run I want to create a simple game with the name "Hammurabi", which uses i.e. Hive content to influence the run of play.
Table of Content:
Getting Content From Hive
In my last documentation I showed, how I have created a code in Angular, which is able to do some basic CRUD (Create, Read, Update, Delete)-functions.
Now I want to get the first content from outside my testlaboratory. Of course I took the Hive-API as an example.
A lot of internet services offer a so called API (Application Programming Interface), so that other services (or people) can read their content in a standard JSON format.
Here is an example, how one can get data from the Hive API:
So, the curl command opens a URL and puts some request-data and we get a result back.
To get more information about the Hive-API, read:
https://developers.hive.io/apidefinitions/#condenser_api.get_discussions_by_author_before_date
In our case, I ask for the last 3 posts, that a user has written. Here I do it with postman:
The result is:
We can do this with Angular also:
Updating the Code
Interface
The content of a Hivepost has a format, which we can see in the curl answer or in the browser:
At the moment we are just interested in one field, namly "created", but later I want to evaluate also other fields.
So, we have to take the efford and create an interface, that maps the JSON fields, we get from the Hive-API.
I did it, by copying the JSON answer and transfering the values into fields:
export interface HiveBlog {
jsonrpc: number,
result:
[{
author: String,
permlink: String,
category: String,
title: String,
body: String,
json_metadata: String[],
created: Date,
last_update: Date,
depth: number,
children: number,
last_payout: Date,
cashout_time: Date,
total_payout_value: String,
curator_payout_value: String,
pending_payout_value: String,
promoted: String,
replies: String[],
body_length: number,
author_reputation: number,
parent_author: String,
parent_permlink: String,
url: String,
root_title: String,
beneficiaries: String[],
max_accepted_payout: String,
percent_hbd: number,
post_id: number,
net_rshares: number,
active_votes: String[]
}]
}
account.service.ts
So, now we can import this structure into our service:
The body I want to send to Hive is:
{"jsonrpc":"2.0", "method":"condenser_api.get_discussions_by_author_before_date", "params":["loginname","","",3], "id":1}
But the loginname shall be replaced by a name, which we can fill in a form. We come later in the html-file back to this. The name is given by requesting the method "getLogindate(name:string)". So we can replace the loginname with this given name:
const body2 = JSON.stringify(body).replace('loginname', `${name}`);
The rest is more or less the same as the other post requests:
import { Injectable } from '@angular/core';
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { Observable, of } from 'rxjs';
import { catchError, map, tap } from 'rxjs/operators';
import { Account } from './account';
import { MessageService } from './message.service';
import { HiveBlog } from './hive-blog';
import { Level1 } from './level1';
@Injectable({
providedIn: 'root'
})
export class AccountService {
private accountsUrl = "http://192.168.2.121:8080/api/accounts"
private accountUrl = "http://192.168.2.121:8080/api/account"
private deleteUrl = "http://192.168.2.121:8080/api/delete"
private hiveBlogUrl = "https://api.hive.blog"
private postContent = "{\"jsonrpc\":\"2.0\", \"method\":\"condenser_api.get_discussions_by_author_before_date\", \"params\":[\"achimmertens\",\"\",\"\",3], \"id\":1}";
httpOptions = {
headers: new HttpHeaders({
'Content-Type': 'application/json',
'Accept': '*/*'
})
};
constructor(
private http: HttpClient,
private messageService: MessageService,
) { }
/** GET accounts from the server */
getAccounts(): Observable<Account[]> {
//var content:Account[]=[];
var content: any;
content = this.http.get<Account[]>(this.accountsUrl)
.pipe(
tap(_ => this.log('fetched accounts')),
catchError(this.handleError<Account[]>('getAccounts', []))
);
this.log("Der Inhalt von content ist:" + JSON.stringify(content));
return content;
}
/* Dies war ein Test um die Daten eine Ebene höher zu betrachten. Wurde aber nicht gebraucht.
getLevel1(): Observable<Level1[]> {
//var content:Level1[]=[];
var content: any;
content = this.http.get<Level1[]>(this.accountsUrl)
.pipe(
tap(_ => this.log('fetched accounts')),
catchError(this.handleError<Level1[]>('getLevel1', []))
);
this.log("Der Inhalt von Content ist:" + JSON.stringify(content));
return JSON.parse(content[0]);
} */
getAccount(id: number): Observable<Account> {
const url = `${this.accountUrl}/${id}`;
return this.http.get<Account>(url).pipe(
tap(_ => this.log(`fetched account id=${id}`)),
catchError(this.handleError<Account>(`getAccount id=${id}`))
);
}
//////// Save methods //////////
/** POST: add a new account to the server */
addAccount(account: Account): Observable<any> {
const body = JSON.stringify(account);
//const body = '{"id":"0","name":"Dummy","nickname":"Achim was here","logindate":"2022-07-27T10:04:29.663Z"}';
//const url = `${this.accountUrl}`;
const url = "http://192.168.2.121:8080/api/account";
console.log("Die url lautet: "+ url);
console.log("Der Body vom Post lautet: " + body);
console.log("Die httpOptions sind: " + JSON.stringify(this.httpOptions));
return this.http.post(url, body, this.httpOptions)
.pipe(
catchError((err) => {
console.error(err);
throw err;
}
))
}
/** DELETE: delete the account from the server */
deleteAccount(id: number): Observable<Account> {
const url = `${this.deleteUrl}/${id}`;
return this.http.delete<Account>(url, this.httpOptions).pipe(
tap(_ => this.log(`deleted account id=${id}`)),
catchError(this.handleError<Account>('deleteAccount'))
);
}
/** -------------------------- Hive Methods --------------------------
* Hive is a Social Media Blockchain. Here we search their API for some transactions
*/
/** GET last logindate in Hive for an Account */
getLogindate(name:string): Observable<HiveBlog> {
const body = {"jsonrpc":"2.0", "method":"condenser_api.get_discussions_by_author_before_date", "params":["loginname","","",3], "id":1};
const body2 = JSON.stringify(body).replace('loginname', `${name}`);
const url = `${this.hiveBlogUrl}`; //const url = "https://api.hive.blog";
console.log("Die url lautet: "+ url);
console.log("Der Body vom Post lautet: " + JSON.stringify(body2));
console.log("Die httpOptions sind: " + JSON.stringify(this.httpOptions));
//return this.http.post(url, body2, this.httpOptions)
return this.http.post<HiveBlog>(url, body2, this.httpOptions)
.pipe(
catchError((err) => {
console.error(err);
throw err;
}
));
}
/* Todo: GetUpvoters
/**
* Handle Http operation that failed.
* Let the app continue.
*
* @param operation - name of the operation that failed
* @param result - optional value to return as the observable result
* */
private handleError<T>(operation = 'operation', result?: T) {
return (error: any): Observable<T> => {
// TODO: send the error to remote logging infrastructure
console.error(error); // log to console instead
// TODO: better job of transforming error for user consumption
this.log(`${operation} failed: ${error.message}`);
// Let the app keep running by returning an empty result.
return of(result as T);
};
}
/** Log a AccountService message with the MessageService */
private log(message: string) {
this.messageService.add(`AccountService: ${message}`);
}
}
account-detail.component.ts:
In this component we use the accountService.getlogindate method. We get a result back and the format of this return value has to be defined first.
Here we could say, that this value shall be saved in the variable "hiveBlog", the type can be any and we start with an empty array:
As I wrote in my last post, this may not be the best way, but it works.
But better we use the right format for hiveBlog:
Now we can create the getLogindate method. We transfer the account.name, which we get from the form in the html code:
This.setLogindate() is a method, which is executed within the subscribe block. This means, it will only be executed, when all data from the accountservice was loaded. If we called this method from somewhere outside the subscription, we had to execute the getlogindate twice (first to get the data and then to fill in the loaded value).
Here is the setLogindate method:
import { Component, OnInit, Input } from '@angular/core';
import { Account } from '../account';
import { ActivatedRoute } from '@angular/router';
import { Location } from '@angular/common';
import { AccountService } from '../account.service';
import { HiveBlog } from '../hive-blog';
import { Observable } from 'rxjs';
@Component({
selector: 'app-account-detail',
templateUrl: './account-detail.component.html',
styleUrls: ['./account-detail.component.css']
})
export class AccountDetailComponent implements OnInit {
account: Account | undefined;
hiveBlog: HiveBlog | undefined;
utcDate: number = 0;
constructor(
private route: ActivatedRoute,
private location: Location,
private accountService: AccountService) { }
ngOnInit(): void {
this.getAccount();
}
/*
ngOnChanges() {
///** WILL TRIGGER WHEN PARENT COMPONENT UPDATES '**
...
}
*/
goBack(): void {
this.location.back();
}
getAccount(): void {
const id = Number(this.route.snapshot.paramMap.get('id'));
this.accountService.getAccount(id)
.subscribe(account => this.account = account);
}
save(): void {
if (this.account) {
this.accountService.addAccount(this.account)
.subscribe(() => this.goBack());
}
}
getLogindate(): void {
if (this.account) {
this.accountService.getLogindate(this.account.name)
//.subscribe(() => this.goBack());
.subscribe((xxx) => {
this.hiveBlog = (xxx);
console.log('this.hiveBlog: ', this.hiveBlog);
this.setLogindate();
});
}
}
setLogindate(): void{
if (this.account){
{
if (this.hiveBlog) {this.account.logindate =new Date(this.hiveBlog.result[0].created)}
}
}
}
}
account-detail.component.html:
Here we have a simple html page, that allows us to see (and change) the content of one of our accounts:
In the browser it looks like this:
The content of the fields is taken from our Java backend and described in my former posts.
But now we want to add content from Hive. By clicking on "get Hive lastlogindate", the value of "Account name" is taken and send to our account.service.ts.
As soon as we get content for "hiveBlog", is will be shown on the page:
<section class="about-section text-center" id="Details">
<div class="container px-4 px-lg-5" *ngIf="account">
<h2>{{account.name | uppercase}} Details</h2>
<div class="badge">
<label for="account-id">Account ID: </label>
<input id="account-id" [(ngModel)]="account.id" placeholder="id">
<label for="account-name">Account name: </label>
<input id="account-name" [(ngModel)]="account.name" placeholder="name">
<label for="account-nickname"> Nickname: </label>
<input id="account-nickname" [(ngModel)]="account.nickname" placeholder="nickname">
<label for="account-logindate"> Logindate: </label>
<input id="account-logindate" [(ngModel)]="account.logindate" placeholder="logindate">
</div>
<button class="button" type="button" (click)="goBack()">go back</button>
<button class="button" type="button" (click)="save()">save</button>
<button class="button" type="button" (click)="getLogindate()">get Hive lastlogindate</button>
</div>
<div class="badge" *ngIf="hiveBlog">
<p> Creationdate of last hiveBlog: {{hiveBlog.result[0].created}} </p>
</div>
</section>
Result
You can find the complete code on: github.
When we type in an in Hive existing account name and press on the "get Hive Lastlogindate" button, we get the date of the last created post of this Hive account:
By clicking on "get Hive lastlogindate" we get the content of the last three posts on Hive.
From the latest post, we take the creationdate and put it into the field "logindate" from our account object. And then we can save this into our own database.
With this the chain is closed. Now we are able to take any web content that exists via the API interface in the internet. We can use Hive content, stock market values, the wheather, my solary batterie status, some memes, cat content,… and put it into our game.
But first, I want to make the style nicer and put some logic into the code, so that we can start calling this code as a game.
So stay tuned,
@jaraumoses: I hope it was ok, to take your post as an example ;-)
Achim Mertens
Always enlightening to see how other developers craft their code and solve a problem. Very detailed, which I love. Delighted you shared.
Great work Achim - looks like much effort!
I am looking forward to see the ongoin proccess!
!HBITS !LUV
Thank you. Yes it was a long way until here!
I belive that! I am looking forward to it - as you may know, all this code sounds like "train station" for me 👀🤣
(1/1) gave you LUV. tools | wallet | discord | community | <>< daily
HiveBuzz.me NFT for Peace
Keep going, good stuff
Amazing progress in the development.
Courage and Peace
The rewards earned on this comment will go directly to the people sharing the post on Twitter as long as they are registered with @poshtoken. Sign up at https://hiveposh.com.
Congratulations @achimmertens! You have completed the following achievement on the Hive blockchain and have been rewarded with new badge(s):
Your next target is to reach 62000 upvotes.
You can view your badges on your board and compare yourself to others in the Ranking
If you no longer want to receive notifications, reply to this comment with the word
STOP
To support your work, I also upvoted your post!
Check out the last post from @hivebuzz:
Support the HiveBuzz project. Vote for our proposal!
The campaign aims to onboard new application developers to grow our ecosystem. If you missed the presentation, you can watch it on YouTube.
You cast your vote for the proposal on Peakd, Ecency, or using HiveSigner.
Thank you!Dear @achimmertens,May I ask you to review and support the Dev Marketing Proposal (https://peakd.com/me/proposals/232) we presented on Conference Day 1 at HiveFest?
Hi Achim.
This is great content. I am so green at Coding. Well done with such great work. It must have taken you a long time to craft such code. You are inspiration. Keep it up!