Merge branch 'spencer/metadata-identifiers' into 'master'
Add meta update cli tool. See merge request grassrootseconomics/cic-internal-integration!138
This commit is contained in:
commit
b0a6df0177
51
apps/cic-meta/bin/get.js
Executable file
51
apps/cic-meta/bin/get.js
Executable 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
81
apps/cic-meta/bin/set.js
Executable 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'});
|
||||||
|
}
|
4232
apps/cic-meta/package-lock.json
generated
4232
apps/cic-meta/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -1,9 +1,14 @@
|
|||||||
{
|
{
|
||||||
"name": "cic-client-meta",
|
"name": "@cicnet/cic-client-meta",
|
||||||
"version": "0.0.7-alpha.8",
|
"version": "0.0.11",
|
||||||
"description": "Signed CRDT metadata graphs for the CIC network",
|
"description": "Signed CRDT metadata graphs for the CIC network",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"types": "dist/index.d.ts",
|
"types": "dist/index.d.ts",
|
||||||
|
"bin": {
|
||||||
|
"meta-set": "bin/set.js",
|
||||||
|
"meta-get": "bin/get.js"
|
||||||
|
},
|
||||||
|
"preferGlobal": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "mocha -r node_modules/node-localstorage/register -r ts-node/register tests/*.ts",
|
"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",
|
"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",
|
"pack": "node_modules/typescript/bin/tsc -d --outDir dist && webpack",
|
||||||
"clean": "rm -rf dist",
|
"clean": "rm -rf dist",
|
||||||
"prepare": "npm run build && npm run build-server",
|
"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": {
|
"dependencies": {
|
||||||
|
"@cicnet/crdt-meta": "^0.0.10",
|
||||||
"@ethereumjs/tx": "^3.0.0-beta.1",
|
"@ethereumjs/tx": "^3.0.0-beta.1",
|
||||||
"automerge": "^0.14.1",
|
"automerge": "^0.14.1",
|
||||||
"crdt-meta": "0.0.8",
|
"colors": "^1.4.0",
|
||||||
"ethereumjs-wallet": "^1.0.1",
|
"ethereumjs-wallet": "^1.0.1",
|
||||||
"ini": "^1.3.8",
|
"ini": "^1.3.8",
|
||||||
"openpgp": "^4.10.8",
|
"openpgp": "^4.10.8",
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { Config } from 'crdt-meta';
|
import { Config } from '@cicnet/crdt-meta';
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
|
|
||||||
if (process.argv[2] === undefined) {
|
if (process.argv[2] === undefined) {
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import * as Automerge from 'automerge';
|
import * as Automerge from 'automerge';
|
||||||
import * as pgp from 'openpgp';
|
import * as pgp from 'openpgp';
|
||||||
|
|
||||||
import { Envelope, Syncable } from 'crdt-meta';
|
import { Envelope, Syncable } from '@cicnet/crdt-meta';
|
||||||
|
|
||||||
|
|
||||||
function handleNoMergeGet(db, digest, keystore) {
|
function handleNoMergeGet(db, digest, keystore) {
|
||||||
|
@ -3,7 +3,8 @@ import * as fs from 'fs';
|
|||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
|
|
||||||
import * as handlers from './handlers';
|
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';
|
import { standardArgs } from './args';
|
||||||
|
|
||||||
|
27
apps/cic-meta/src/custom.ts
Normal file
27
apps/cic-meta/src/custom.ts
Normal 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
90
apps/cic-meta/src/db.ts
Normal 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,
|
||||||
|
}
|
@ -1,2 +1,4 @@
|
|||||||
export { User } from './user';
|
export { User } from './user';
|
||||||
export { Phone } from './phone';
|
export { Phone } from './phone';
|
||||||
|
export { Custom } from './custom';
|
||||||
|
export { Meta } from './meta';
|
||||||
|
126
apps/cic-meta/src/meta.ts
Normal file
126
apps/cic-meta/src/meta.ts
Normal 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,
|
||||||
|
}
|
@ -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 {
|
class Phone extends Syncable implements Addressable {
|
||||||
|
|
||||||
|
@ -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');
|
const keySalt = new TextEncoder().encode(':cic.person');
|
||||||
class User extends Syncable implements Addressable {
|
class User extends Syncable implements Addressable {
|
||||||
|
@ -4,7 +4,8 @@ import pgp = require('openpgp');
|
|||||||
import sqlite = require('sqlite3');
|
import sqlite = require('sqlite3');
|
||||||
|
|
||||||
import * as handlers from '../scripts/server/handlers';
|
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() {
|
function createKeystore() {
|
||||||
const pksa = fs.readFileSync(__dirname + '/privatekeys.asc', 'utf-8');
|
const pksa = fs.readFileSync(__dirname + '/privatekeys.asc', 'utf-8');
|
||||||
|
@ -19,6 +19,7 @@
|
|||||||
"include": [
|
"include": [
|
||||||
"src/**/*",
|
"src/**/*",
|
||||||
"scripts/server/*",
|
"scripts/server/*",
|
||||||
"index.ts"
|
"index.ts",
|
||||||
|
"bin"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -2,8 +2,8 @@ const fs = require('fs');
|
|||||||
const path = require('path');
|
const path = require('path');
|
||||||
const http = require('http');
|
const http = require('http');
|
||||||
|
|
||||||
const cic = require('cic-client-meta');
|
const cic = require('@cicnet/cic-client-meta');
|
||||||
const crdt = require('crdt-meta');
|
const crdt = require('@cicnet/crdt-meta');
|
||||||
|
|
||||||
//const conf = JSON.parse(fs.readFileSync('./cic.conf'));
|
//const conf = JSON.parse(fs.readFileSync('./cic.conf'));
|
||||||
|
|
||||||
|
@ -2,12 +2,12 @@ const fs = require('fs');
|
|||||||
const path = require('path');
|
const path = require('path');
|
||||||
const http = require('http');
|
const http = require('http');
|
||||||
|
|
||||||
const cic = require('cic-client-meta');
|
const cic = require('@cicnet/cic-client-meta');
|
||||||
const vcfp = require('vcard-parser');
|
const crdt = require('@cicnet/crdt-meta');
|
||||||
|
|
||||||
//const conf = JSON.parse(fs.readFileSync('./cic.conf'));
|
//const conf = JSON.parse(fs.readFileSync('./cic.conf'));
|
||||||
|
|
||||||
const config = new cic.Config('./config');
|
const config = new crdt.Config('./config');
|
||||||
config.process();
|
config.process();
|
||||||
console.log(config);
|
console.log(config);
|
||||||
|
|
||||||
@ -42,24 +42,18 @@ function sendit(uid, envelope) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function doOne(keystore, filePath, identifier) {
|
function doOne(keystore, filePath, identifier) {
|
||||||
const signer = new cic.PGPSigner(keystore);
|
const signer = new crdt.PGPSigner(keystore);
|
||||||
|
|
||||||
const o = JSON.parse(fs.readFileSync(filePath).toString());
|
const o = JSON.parse(fs.readFileSync(filePath).toString());
|
||||||
//const b = Buffer.from(j['vcard'], 'base64');
|
|
||||||
//const s = b.toString();
|
|
||||||
//const o = vcfp.parse(s);
|
|
||||||
//const phone = o.tel[0].value;
|
|
||||||
|
|
||||||
//cic.Phone.toKey(phone).then((uid) => {
|
cic.Custom.toKey(identifier).then((uid) => {
|
||||||
//const o = fs.readFileSync(filePath, 'utf-8');
|
const s = new crdt.Syncable(uid, o);
|
||||||
|
|
||||||
const s = new cic.Syncable(identifier, o);
|
|
||||||
s.setSigner(signer);
|
s.setSigner(signer);
|
||||||
s.onwrap = (env) => {
|
s.onwrap = (env) => {
|
||||||
sendit(identifier, env);
|
sendit(identifier, env);
|
||||||
};
|
};
|
||||||
s.sign();
|
s.sign();
|
||||||
//});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const privateKeyPath = path.join(config.get('PGP_EXPORTS_DIR'), config.get('PGP_PRIVATE_KEY_FILE'));
|
const privateKeyPath = path.join(config.get('PGP_EXPORTS_DIR'), config.get('PGP_PRIVATE_KEY_FILE'));
|
||||||
@ -67,7 +61,7 @@ const publicKeyPath = path.join(config.get('PGP_EXPORTS_DIR'), config.get('PGP_P
|
|||||||
pk = fs.readFileSync(privateKeyPath);
|
pk = fs.readFileSync(privateKeyPath);
|
||||||
pubk = fs.readFileSync(publicKeyPath);
|
pubk = fs.readFileSync(publicKeyPath);
|
||||||
|
|
||||||
new cic.PGPKeyStore(
|
new crdt.PGPKeyStore(
|
||||||
config.get('PGP_PASSPHRASE'),
|
config.get('PGP_PASSPHRASE'),
|
||||||
pk,
|
pk,
|
||||||
pubk,
|
pubk,
|
||||||
@ -94,7 +88,7 @@ function importMetaCustom(keystore) {
|
|||||||
err, files = fs.readdirSync(workDir);
|
err, files = fs.readdirSync(workDir);
|
||||||
} catch {
|
} catch {
|
||||||
console.error('source directory not yet ready', workDir);
|
console.error('source directory not yet ready', workDir);
|
||||||
setTimeout(importMetaPhone, batchDelay, keystore);
|
setTimeout(importMetaCustom, batchDelay, keystore);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let limit = batchSize;
|
let limit = batchSize;
|
||||||
@ -128,7 +122,7 @@ function importMetaCustom(keystore) {
|
|||||||
if (batchCount == batchSize) {
|
if (batchCount == batchSize) {
|
||||||
console.debug('reached batch size, breathing');
|
console.debug('reached batch size, breathing');
|
||||||
batchCount=0;
|
batchCount=0;
|
||||||
setTimeout(importMeta, batchDelay, keystore);
|
setTimeout(importMetaCustom, batchDelay, keystore);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,9 +2,9 @@ const fs = require('fs');
|
|||||||
const path = require('path');
|
const path = require('path');
|
||||||
const http = require('http');
|
const http = require('http');
|
||||||
|
|
||||||
const cic = require('cic-client-meta');
|
const cic = require('@cicnet/cic-client-meta');
|
||||||
const vcfp = require('vcard-parser');
|
const vcfp = require('vcard-parser');
|
||||||
const crdt = require('crdt-meta');
|
const crdt = require('@cicnet/crdt-meta');
|
||||||
|
|
||||||
//const conf = JSON.parse(fs.readFileSync('./cic.conf'));
|
//const conf = JSON.parse(fs.readFileSync('./cic.conf'));
|
||||||
|
|
||||||
|
147
apps/data-seeding/package-lock.json
generated
147
apps/data-seeding/package-lock.json
generated
@ -5,10 +5,49 @@
|
|||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"cic-client-meta": "0.0.7-alpha.6",
|
"@cicnet/cic-client-meta": "^0.0.11",
|
||||||
|
"@cicnet/crdt-meta": "^0.0.10",
|
||||||
"vcard-parser": "^1.0.0"
|
"vcard-parser": "^1.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@cicnet/cic-client-meta": {
|
||||||
|
"version": "0.0.11",
|
||||||
|
"resolved": "https://registry.npmjs.org/@cicnet/cic-client-meta/-/cic-client-meta-0.0.11.tgz",
|
||||||
|
"integrity": "sha512-RL9CPXkWQBQzaqMoldnENC/xqinIMyWNSsejs9+qkFOWbAvC4inLdpjjCaooyvLpIZGHF9cLjxHiAdBMR9L8sQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"@cicnet/crdt-meta": "^0.0.10",
|
||||||
|
"@ethereumjs/tx": "^3.0.0-beta.1",
|
||||||
|
"automerge": "^0.14.1",
|
||||||
|
"colors": "^1.4.0",
|
||||||
|
"ethereumjs-wallet": "^1.0.1",
|
||||||
|
"ini": "^1.3.8",
|
||||||
|
"openpgp": "^4.10.8",
|
||||||
|
"pg": "^8.4.2",
|
||||||
|
"sqlite3": "^5.0.0",
|
||||||
|
"yargs": "^16.1.0"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"meta-get": "bin/get.js",
|
||||||
|
"meta-set": "bin/set.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.16.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@cicnet/crdt-meta": {
|
||||||
|
"version": "0.0.10",
|
||||||
|
"resolved": "https://registry.npmjs.org/@cicnet/crdt-meta/-/crdt-meta-0.0.10.tgz",
|
||||||
|
"integrity": "sha512-f+H6BQA2tE718KuNYiNzrDJN4wY00zeuhXM6aPKJUX6nryzX9g2r0yf8iDhkz+Fts1R6M7Riz73MfFEa8fgvsw==",
|
||||||
|
"dependencies": {
|
||||||
|
"automerge": "^0.14.2",
|
||||||
|
"ini": "^1.3.8",
|
||||||
|
"openpgp": "^4.10.8",
|
||||||
|
"readline-sync": "^1.4.10"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.16.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@ethereumjs/common": {
|
"node_modules/@ethereumjs/common": {
|
||||||
"version": "2.2.0",
|
"version": "2.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/@ethereumjs/common/-/common-2.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/@ethereumjs/common/-/common-2.2.0.tgz",
|
||||||
@ -317,24 +356,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz",
|
"resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz",
|
||||||
"integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg=="
|
"integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg=="
|
||||||
},
|
},
|
||||||
"node_modules/cic-client-meta": {
|
|
||||||
"version": "0.0.7-alpha.6",
|
|
||||||
"resolved": "https://registry.npmjs.org/cic-client-meta/-/cic-client-meta-0.0.7-alpha.6.tgz",
|
|
||||||
"integrity": "sha512-oIN1aHkPHfsxJKDV6k4f1kX2tcppw3Q+D1b4BoPh0hYjNKNb7gImBMWnGsy8uiD9W6SNYE4sIXyrtct8mvrhsw==",
|
|
||||||
"dependencies": {
|
|
||||||
"@ethereumjs/tx": "^3.0.0-beta.1",
|
|
||||||
"automerge": "^0.14.1",
|
|
||||||
"ethereumjs-wallet": "^1.0.1",
|
|
||||||
"ini": "^1.3.5",
|
|
||||||
"openpgp": "^4.10.8",
|
|
||||||
"pg": "^8.4.2",
|
|
||||||
"sqlite3": "^5.0.0",
|
|
||||||
"yargs": "^16.1.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": "~14.16.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/cipher-base": {
|
"node_modules/cipher-base": {
|
||||||
"version": "1.0.4",
|
"version": "1.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz",
|
||||||
@ -418,6 +439,14 @@
|
|||||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
||||||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
|
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
|
||||||
},
|
},
|
||||||
|
"node_modules/colors": {
|
||||||
|
"version": "1.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz",
|
||||||
|
"integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.1.90"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/combined-stream": {
|
"node_modules/combined-stream": {
|
||||||
"version": "1.0.8",
|
"version": "1.0.8",
|
||||||
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
||||||
@ -1555,6 +1584,14 @@
|
|||||||
"node": ">= 6"
|
"node": ">= 6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/readline-sync": {
|
||||||
|
"version": "1.4.10",
|
||||||
|
"resolved": "https://registry.npmjs.org/readline-sync/-/readline-sync-1.4.10.tgz",
|
||||||
|
"integrity": "sha512-gNva8/6UAe8QYepIQH/jQ2qn91Qj0B9sYjMBBs3QOB8F2CXcKgLxQaJRP76sWVRQt+QU+8fAkCbCvjjMFu7Ycw==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.8.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/request": {
|
"node_modules/request": {
|
||||||
"version": "2.88.2",
|
"version": "2.88.2",
|
||||||
"resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz",
|
"resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz",
|
||||||
@ -2085,6 +2122,34 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@cicnet/cic-client-meta": {
|
||||||
|
"version": "0.0.11",
|
||||||
|
"resolved": "https://registry.npmjs.org/@cicnet/cic-client-meta/-/cic-client-meta-0.0.11.tgz",
|
||||||
|
"integrity": "sha512-RL9CPXkWQBQzaqMoldnENC/xqinIMyWNSsejs9+qkFOWbAvC4inLdpjjCaooyvLpIZGHF9cLjxHiAdBMR9L8sQ==",
|
||||||
|
"requires": {
|
||||||
|
"@cicnet/crdt-meta": "^0.0.10",
|
||||||
|
"@ethereumjs/tx": "^3.0.0-beta.1",
|
||||||
|
"automerge": "^0.14.1",
|
||||||
|
"colors": "^1.4.0",
|
||||||
|
"ethereumjs-wallet": "^1.0.1",
|
||||||
|
"ini": "^1.3.8",
|
||||||
|
"openpgp": "^4.10.8",
|
||||||
|
"pg": "^8.4.2",
|
||||||
|
"sqlite3": "^5.0.0",
|
||||||
|
"yargs": "^16.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@cicnet/crdt-meta": {
|
||||||
|
"version": "0.0.10",
|
||||||
|
"resolved": "https://registry.npmjs.org/@cicnet/crdt-meta/-/crdt-meta-0.0.10.tgz",
|
||||||
|
"integrity": "sha512-f+H6BQA2tE718KuNYiNzrDJN4wY00zeuhXM6aPKJUX6nryzX9g2r0yf8iDhkz+Fts1R6M7Riz73MfFEa8fgvsw==",
|
||||||
|
"requires": {
|
||||||
|
"automerge": "^0.14.2",
|
||||||
|
"ini": "^1.3.8",
|
||||||
|
"openpgp": "^4.10.8",
|
||||||
|
"readline-sync": "^1.4.10"
|
||||||
|
}
|
||||||
|
},
|
||||||
"@ethereumjs/common": {
|
"@ethereumjs/common": {
|
||||||
"version": "2.2.0",
|
"version": "2.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/@ethereumjs/common/-/common-2.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/@ethereumjs/common/-/common-2.2.0.tgz",
|
||||||
@ -2112,9 +2177,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@types/node": {
|
"@types/node": {
|
||||||
"version": "14.14.39",
|
"version": "14.14.41",
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.39.tgz",
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.41.tgz",
|
||||||
"integrity": "sha512-Qipn7rfTxGEDqZiezH+wxqWYR8vcXq5LRpZrETD19Gs4o8LbklbmqotSUsMU+s5G3PJwMRDfNEYoxrcBwIxOuw=="
|
"integrity": "sha512-dueRKfaJL4RTtSa7bWeTK1M+VH+Gns73oCgzvYfHZywRCoPSd8EkXBL0mZ9unPTveBn+D9phZBaxuzpwjWkW0g=="
|
||||||
},
|
},
|
||||||
"@types/pbkdf2": {
|
"@types/pbkdf2": {
|
||||||
"version": "3.1.0",
|
"version": "3.1.0",
|
||||||
@ -2379,22 +2444,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz",
|
"resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz",
|
||||||
"integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg=="
|
"integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg=="
|
||||||
},
|
},
|
||||||
"cic-client-meta": {
|
|
||||||
"version": "0.0.7-alpha.8",
|
|
||||||
"resolved": "https://registry.npmjs.org/cic-client-meta/-/cic-client-meta-0.0.7-alpha.8.tgz",
|
|
||||||
"integrity": "sha512-NtU4b4dptG2gsKXIvAv1xCxxxhrr801tb8+Co1O+VLx+wvxFyPRxqa2f2eN5nrSnFnljNsWWpE6K5bJZb1+Rqw==",
|
|
||||||
"requires": {
|
|
||||||
"@ethereumjs/tx": "^3.0.0-beta.1",
|
|
||||||
"automerge": "^0.14.1",
|
|
||||||
"crdt-meta": "0.0.8",
|
|
||||||
"ethereumjs-wallet": "^1.0.1",
|
|
||||||
"ini": "^1.3.8",
|
|
||||||
"openpgp": "^4.10.8",
|
|
||||||
"pg": "^8.4.2",
|
|
||||||
"sqlite3": "^5.0.0",
|
|
||||||
"yargs": "^16.1.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"cipher-base": {
|
"cipher-base": {
|
||||||
"version": "1.0.4",
|
"version": "1.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz",
|
||||||
@ -2462,6 +2511,11 @@
|
|||||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
||||||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
|
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
|
||||||
},
|
},
|
||||||
|
"colors": {
|
||||||
|
"version": "1.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz",
|
||||||
|
"integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA=="
|
||||||
|
},
|
||||||
"combined-stream": {
|
"combined-stream": {
|
||||||
"version": "1.0.8",
|
"version": "1.0.8",
|
||||||
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
||||||
@ -2495,18 +2549,6 @@
|
|||||||
"printj": "~1.1.0"
|
"printj": "~1.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"crdt-meta": {
|
|
||||||
"version": "0.0.8",
|
|
||||||
"resolved": "https://registry.npmjs.org/crdt-meta/-/crdt-meta-0.0.8.tgz",
|
|
||||||
"integrity": "sha512-CS0sS0L2QWthz7vmu6vzl3p4kcpJ+IKILBJ4tbgN4A3iNG8wnBeuDIv/z3KFFQjcfuP4QAh6E9LywKUTxtDc3g==",
|
|
||||||
"requires": {
|
|
||||||
"automerge": "^0.14.2",
|
|
||||||
"ini": "^1.3.8",
|
|
||||||
"openpgp": "^4.10.8",
|
|
||||||
"pg": "^8.5.1",
|
|
||||||
"sqlite3": "^5.0.2"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"create-hash": {
|
"create-hash": {
|
||||||
"version": "1.2.0",
|
"version": "1.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz",
|
||||||
@ -3415,6 +3457,11 @@
|
|||||||
"util-deprecate": "^1.0.1"
|
"util-deprecate": "^1.0.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"readline-sync": {
|
||||||
|
"version": "1.4.10",
|
||||||
|
"resolved": "https://registry.npmjs.org/readline-sync/-/readline-sync-1.4.10.tgz",
|
||||||
|
"integrity": "sha512-gNva8/6UAe8QYepIQH/jQ2qn91Qj0B9sYjMBBs3QOB8F2CXcKgLxQaJRP76sWVRQt+QU+8fAkCbCvjjMFu7Ycw=="
|
||||||
|
},
|
||||||
"request": {
|
"request": {
|
||||||
"version": "2.88.2",
|
"version": "2.88.2",
|
||||||
"resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz",
|
"resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz",
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"cic-client-meta": "^0.0.7-alpha.8",
|
"@cicnet/cic-client-meta": "^0.0.11",
|
||||||
"crdt-meta": "0.0.8",
|
"@cicnet/crdt-meta": "^0.0.10",
|
||||||
"vcard-parser": "^1.0.0"
|
"vcard-parser": "^1.0.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user