Add meta update cli tool.

This commit is contained in:
Spencer Ofwiti
2021-05-21 09:42:08 +00:00
parent d7113f3923
commit 92c9df4e19
20 changed files with 4659 additions and 177 deletions

51
apps/cic-meta/bin/get.js Executable file
View File

@@ -0,0 +1,51 @@
#!/usr/bin/env node
const colors = require('colors');
const {Meta} = require("../dist");
let { argv } = require('yargs')
.usage('Usage: $0 -m http://localhost:63380 -n publickeys')
.example(
'$0 -m http://localhost:63380 -n publickeys',
'Fetches the public keys blob from the meta server'
)
.option('m', {
alias: 'metaurl',
describe: 'The URL for the meta service',
demandOption: 'The meta url is required',
type: 'string',
nargs: 1,
})
.option('n', {
alias: 'name',
describe: 'The name of the resource to be fetched from the meta service',
demandOption: 'The name of the resource is required',
type: 'string',
nargs: 1,
})
.option('t', {
alias: 'type',
describe: 'The type of resource to be fetched from the meta service\n' +
'Options: `user`, `phone` and `custom`\n' +
'Defaults to `custom`',
type: 'string',
nargs: 1,
})
.epilog('Grassroots Economics (c) 2021')
.wrap(null);
const metaUrl = argv.m;
const resourceName = argv.n;
let type = argv.t;
if (type === undefined) {
type = 'custom'
}
(async () => {
const identifier = await Meta.getIdentifier(resourceName, type);
console.log(colors.cyan(`Meta server storage identifier: ${identifier}`));
const metaResponse = await Meta.get(identifier, metaUrl);
if (typeof metaResponse !== "object") {
console.error(colors.red('Metadata get failed!'));
}
console.log(colors.green(metaResponse));
})();

81
apps/cic-meta/bin/set.js Executable file
View File

@@ -0,0 +1,81 @@
#!/usr/bin/env node
const fs = require("fs");
const colors = require('colors');
const {Meta} = require("../dist");
let { argv } = require('yargs')
.usage('Usage: $0 -m http://localhost:63380 -k ./privatekeys.asc -n publickeys -r ./publickeys.asc')
.example(
'$0 -m http://localhost:63380 -k ./privatekeys.asc -n publickeys -r ./publickeys.asc',
'Updates the public keys blob to the meta server'
)
.option('m', {
alias: 'metaurl',
describe: 'The URL for the meta service',
demandOption: 'The meta url is required',
type: 'string',
nargs: 1,
})
.option('k', {
alias: 'privatekey',
describe: 'The PGP private key blob file used to sign the changes to the meta service',
demandOption: 'The private key file is required',
type: 'string',
nargs: 1,
})
.option('n', {
alias: 'name',
describe: 'The name of the resource to be set or updated to the meta service',
demandOption: 'The name of the resource is required',
type: 'string',
nargs: 1,
})
.option('r', {
alias: 'resource',
describe: 'The resource file to be set or updated to the meta service',
demandOption: 'The resource file is required',
type: 'string',
nargs: 1,
})
.option('t', {
alias: 'type',
describe: 'The type of resource to be set or updated to the meta service\n' +
'Options: `user`, `phone` and `custom`\n' +
'Defaults to `custom`',
type: 'string',
nargs: 1,
})
.epilog('Grassroots Economics (c) 2021')
.wrap(null);
const metaUrl = argv.m;
const privateKeyFile = argv.k;
const resourceName = argv.n;
const resourceFile = argv.r;
let type = argv.t;
if (type === undefined) {
type = 'custom'
}
const privateKey = readFile(privateKeyFile);
const resource = readFile(resourceFile);
(async () => {
if (privateKey && resource) {
const identifier = await Meta.getIdentifier(resourceName, type);
console.log(colors.cyan(`Meta server storage identifier: ${identifier}`));
const meta = new Meta(metaUrl, privateKey);
meta.onload = async (status) => {
const response = await meta.set(identifier, resource)
console.log(colors.green(response));
}
}
})();
function readFile(filename) {
if(!fs.existsSync(filename)) {
console.log(colors.red(`File ${filename} not found`));
return;
}
return fs.readFileSync(filename, {encoding: 'utf8', flag: 'r'});
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,9 +1,14 @@
{
"name": "cic-client-meta",
"version": "0.0.7-alpha.8",
"name": "@cicnet/cic-client-meta",
"version": "0.0.11",
"description": "Signed CRDT metadata graphs for the CIC network",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"bin": {
"meta-set": "bin/set.js",
"meta-get": "bin/get.js"
},
"preferGlobal": true,
"scripts": {
"test": "mocha -r node_modules/node-localstorage/register -r ts-node/register tests/*.ts",
"build": "node_modules/typescript/bin/tsc -d --outDir dist src/index.ts",
@@ -11,12 +16,14 @@
"pack": "node_modules/typescript/bin/tsc -d --outDir dist && webpack",
"clean": "rm -rf dist",
"prepare": "npm run build && npm run build-server",
"start": "./node_modules/ts-node/dist/bin.js ./scripts/server/server.ts"
"start": "./node_modules/ts-node/dist/bin.js ./scripts/server/server.ts",
"publish": "npm publish --access public"
},
"dependencies": {
"@cicnet/crdt-meta": "^0.0.10",
"@ethereumjs/tx": "^3.0.0-beta.1",
"automerge": "^0.14.1",
"crdt-meta": "0.0.8",
"colors": "^1.4.0",
"ethereumjs-wallet": "^1.0.1",
"ini": "^1.3.8",
"openpgp": "^4.10.8",

View File

@@ -1,4 +1,4 @@
import { Config } from 'crdt-meta';
import { Config } from '@cicnet/crdt-meta';
const fs = require('fs');
if (process.argv[2] === undefined) {

View File

@@ -1,7 +1,7 @@
import * as Automerge from 'automerge';
import * as pgp from 'openpgp';
import { Envelope, Syncable } from 'crdt-meta';
import { Envelope, Syncable } from '@cicnet/crdt-meta';
function handleNoMergeGet(db, digest, keystore) {

View File

@@ -3,7 +3,8 @@ import * as fs from 'fs';
import * as path from 'path';
import * as handlers from './handlers';
import { PGPKeyStore, PGPSigner, Config, SqliteAdapter, PostgresAdapter } from 'crdt-meta';
import { PGPKeyStore, PGPSigner, Config } from '@cicnet/crdt-meta';
import { SqliteAdapter, PostgresAdapter } from '../../src/db';
import { standardArgs } from './args';

View File

@@ -0,0 +1,27 @@
import {Addressable, mergeKey, Syncable} from "@cicnet/crdt-meta";
class Custom extends Syncable implements Addressable {
name: string
value: Object
constructor(name:string, v:Object={}) {
super('', v);
Custom.toKey(name).then((cid) => {
this.id = cid;
this.value = v;
});
}
public static async toKey(item:string, identifier: string = ':cic.custom') {
return await mergeKey(Buffer.from(item), Buffer.from(identifier));
}
public key(): string {
return this.id;
}
}
export {
Custom,
}

90
apps/cic-meta/src/db.ts Normal file
View File

@@ -0,0 +1,90 @@
import * as pg from 'pg';
import * as sqlite from 'sqlite3';
type DbConfig = {
name: string
host: string
port: number
user: string
password: string
}
interface DbAdapter {
query: (s:string, callback:(e:any, rs:any) => void) => void
close: () => void
}
const re_creatematch = /^(CREATE)/i
const re_getmatch = /^(SELECT)/i;
const re_setmatch = /^(INSERT|UPDATE)/i;
class SqliteAdapter implements DbAdapter {
db: any
constructor(dbConfig:DbConfig, callback?:(any) => void) {
this.db = new sqlite.Database(dbConfig.name); //, callback);
}
public query(s:string, callback:(e:any, rs?:any) => void): void {
const local_callback = (e, rs) => {
let r = undefined;
if (rs !== undefined) {
r = {
rowCount: rs.length,
rows: rs,
}
}
callback(e, r);
};
if (s.match(re_getmatch)) {
this.db.all(s, local_callback);
} else if (s.match(re_setmatch)) {
this.db.run(s, local_callback);
} else if (s.match(re_creatematch)) {
this.db.run(s, callback);
} else {
throw 'unhandled query';
}
}
public close() {
this.db.close();
}
}
class PostgresAdapter implements DbAdapter {
db: any
constructor(dbConfig:DbConfig) {
let o = dbConfig;
o['database'] = o.name;
this.db = new pg.Pool(o);
return this.db;
}
public query(s:string, callback:(e:any, rs:any) => void): void {
this.db.query(s, (e, rs) => {
let r = {
length: rs.rowCount,
}
rs.length = rs.rowCount;
if (e === undefined) {
e = null;
}
console.debug(e, rs);
callback(e, rs);
});
}
public close() {
this.db.end();
}
}
export {
DbConfig,
SqliteAdapter,
PostgresAdapter,
}

View File

@@ -1,2 +1,4 @@
export { User } from './user';
export { Phone } from './phone';
export { Custom } from './custom';
export { Meta } from './meta';

126
apps/cic-meta/src/meta.ts Normal file
View File

@@ -0,0 +1,126 @@
import {ArgPair, Envelope, Syncable, MutablePgpKeyStore, PGPSigner} from "@cicnet/crdt-meta";
import {User} from "./user";
import {Phone} from "./phone";
import {Custom} from "./custom";
const fetch = require("node-fetch");
const headers = {
'Content-Type': 'application/json;charset=utf-8',
'x-cic-automerge': 'client'
};
const options = {
headers: headers,
};
class Meta {
keystore: MutablePgpKeyStore = new MutablePgpKeyStore();
signer: PGPSigner = new PGPSigner(this.keystore);
metaUrl: string;
private privateKey: string;
onload: (status: boolean) => void;
constructor(metaUrl: string, privateKey: any) {
this.metaUrl = metaUrl;
this.privateKey = privateKey;
this.keystore.loadKeyring().then(() => {
this.keystore.importPrivateKey(privateKey).then(() => this.onload(true));
});
}
async set(identifier: string, data: Object): Promise<any> {
let syncable: Syncable;
const response = await Meta.get(identifier, this.metaUrl);
if (response === `Request to ${this.metaUrl}/${identifier} failed. Connection error.`) {
return response;
} else if (typeof response !== "object" || typeof data !== "object") {
syncable = new Syncable(identifier, data);
const res = await this.updateMeta(syncable, identifier);
return `${res.status}: ${res.statusText}`;
} else {
syncable = await Meta.get(identifier, this.metaUrl);
let update: Array<ArgPair> = [];
for (const prop in data) {
update.push(new ArgPair(prop, data[prop]));
}
syncable.update(update, 'client-branch');
const res = await this.updateMeta(syncable, identifier);
return `${res.status}: ${res.statusText}`;
}
}
async updateMeta(syncable: Syncable, identifier: string): Promise<any> {
const envelope: Envelope = await this.wrap(syncable);
const reqBody: string = envelope.toJSON();
const putOptions = {
method: 'PUT',
headers: headers,
body: reqBody
};
return await fetch(`${this.metaUrl}/${identifier}`, putOptions).then(async response => {
if (response.ok) {
return Promise.resolve({
status: response.status,
statusText: response.statusText + ', Metadata updated successfully!'
});
} else {
return Promise.reject({
status: response.status,
statusText: response.statusText
});
}
});
}
static async get(identifier: string, metaUrl: string): Promise<any> {
const response = await fetch(`${metaUrl}/${identifier}`, options).then(response => {
if (response.ok) {
return (response.json());
} else {
return Promise.reject({
status: response.status,
statusText: response.statusText
});
}
}).catch(error => {
if (error.code === 'ECONNREFUSED') {
return `Request to ${metaUrl}/${identifier} failed. Connection error.`
}
return `${error.status}: ${error.statusText}`;
});
if (typeof response !== "object") {
return response;
}
return Envelope.fromJSON(JSON.stringify(response)).unwrap();
}
static async getIdentifier(name: string, type: string = 'custom'): Promise<string> {
let identifier: string;
type = type.toLowerCase();
if (type === 'user') {
identifier = await User.toKey(name);
} else if (type === 'phone') {
identifier = await Phone.toKey(name);
} else {
identifier = await Custom.toKey(name);
}
return identifier;
}
private wrap(syncable: Syncable): Promise<Envelope> {
return new Promise<Envelope>(async (resolve, reject) => {
syncable.setSigner(this.signer);
syncable.onwrap = async (env) => {
if (env === undefined) {
reject();
return;
}
resolve(env);
};
syncable.sign();
});
}
}
export {
Meta,
}

View File

@@ -1,4 +1,4 @@
import { Syncable, Addressable, mergeKey } from 'crdt-meta';
import { Syncable, Addressable, mergeKey } from '@cicnet/crdt-meta';
class Phone extends Syncable implements Addressable {

View File

@@ -1,4 +1,4 @@
import { Syncable, Addressable, toAddressKey } from 'crdt-meta';
import { Syncable, Addressable, toAddressKey } from '@cicnet/crdt-meta';
const keySalt = new TextEncoder().encode(':cic.person');
class User extends Syncable implements Addressable {

View File

@@ -4,7 +4,8 @@ import pgp = require('openpgp');
import sqlite = require('sqlite3');
import * as handlers from '../scripts/server/handlers';
import { Envelope, Syncable, ArgPair, PGPKeyStore, PGPSigner, KeyStore, Signer, SqliteAdapter } from 'crdt-meta';
import { Envelope, Syncable, ArgPair, PGPKeyStore, PGPSigner, KeyStore, Signer } from '@cicnet/crdt-meta';
import { SqliteAdapter } from '../src/db';
function createKeystore() {
const pksa = fs.readFileSync(__dirname + '/privatekeys.asc', 'utf-8');

View File

@@ -19,6 +19,7 @@
"include": [
"src/**/*",
"scripts/server/*",
"index.ts"
"index.ts",
"bin"
]
}