diff --git a/BackgroundServices/BackgroundServices.csproj b/BackgroundServices/BackgroundServices.csproj index 1680a04..23e5563 100644 --- a/BackgroundServices/BackgroundServices.csproj +++ b/BackgroundServices/BackgroundServices.csproj @@ -7,7 +7,7 @@ - + diff --git a/BackgroundServices/Calculator.cs b/BackgroundServices/Calculator.cs index 0315d4a..e400ab7 100644 --- a/BackgroundServices/Calculator.cs +++ b/BackgroundServices/Calculator.cs @@ -2,23 +2,29 @@ using System.Collections.Generic; using System.Linq; using CryptoManager.Models; +using Hangfire; +using Microsoft.EntityFrameworkCore; using Model.DbModels; using Model.Enums; +using Plugins; namespace BackgroundServices { public class Calculator { private readonly CryptoContext _context; - private static readonly string[] FiatCurrencies = { "EUR", "CHF", "USD" }; + private readonly IMarketData _marketData; - public Calculator(CryptoContext context) + public Calculator(CryptoContext context, IMarketData marketData) { _context = context; + _marketData = marketData; } public void RecalculateAll() { + BackgroundJob.Enqueue(c => c.CalculateFlow()); + var transactions = _context.Transactions; var wallets = new Dictionary>(); //var fiatInvestments = new Dictionary>(); @@ -32,7 +38,7 @@ public void RecalculateAll() ? fiatDict[transaction.ExchangeId] : new Dictionary(); - + switch (transaction.Type) { case TransactionType.Trade: @@ -81,7 +87,7 @@ public void RecalculateAll() } // Is this a FiatBalance - if (FiatCurrencies.Contains(transaction.InCurrency)) + if (_marketData.IsFiat(transaction.InCurrency)) { if (fiatEx.ContainsKey(transaction.InCurrency)) { @@ -91,7 +97,7 @@ public void RecalculateAll() { fiatEx.Add(transaction.InCurrency, new Fiat { - Investments = transaction.InAmount + Investments = transaction.InAmount }); } } @@ -108,7 +114,7 @@ public void RecalculateAll() } // Is this a FiatBalance - if (FiatCurrencies.Contains(transaction.OutCurrency)) + if (_marketData.IsFiat(transaction.OutCurrency)) { if (fiatEx.ContainsKey(transaction.OutCurrency)) { @@ -194,6 +200,192 @@ public void RecalculateAll() } + public void CalculateFlow() + { + // Truncate old data from table + _context.FlowNodes.RemoveRange(_context.FlowNodes); + _context.FlowLinks.RemoveRange(_context.FlowLinks); + _context.SaveChanges(); + + + // Initialize Buckets per Exchange + var allNodes = new Dictionary>(); + var allLinks = new Dictionary>(); + + foreach (var exchange in _context.Exchanges) + { + var links = new List(); + + // Add all Inputs + var inputs = + _context.Transactions.Where(t => t.ExchangeId == exchange.Id && t.Type == TransactionType.In).OrderBy(t => t.DateTime); + var nodes = inputs.Select(input => new FlowNode(input.DateTime, input.InAmount, input.InCurrency, exchange.Id, input.Id, "In")).ToList(); + + // Add all Outputs + var outputs = _context.Transactions.Where(t => t.ExchangeId == exchange.Id && t.Type == TransactionType.Out).OrderBy(t => t.DateTime); + nodes.AddRange(outputs.Select(output => new FlowNode(output.DateTime, output.OutAmount, output.OutCurrency, exchange.Id, output.Id, "Out"))); + + var transactions = _context.Transactions.Where(t => t.ExchangeId == exchange.Id).OrderBy(t => t.DateTime); + + var lastBuckets = new Dictionary(); // Currency/FlowNode Id + + foreach (var transaction in transactions) + { + switch (transaction.Type) + { + case TransactionType.In: + { + if (lastBuckets.ContainsKey(transaction.InCurrency)) + { + // Add Amount to new Bucket + var sourceNode = nodes.Single(n => n.DateTime == transaction.DateTime && n.Amount == transaction.InAmount); + + var oldNode = nodes.Single(n => n.Id == lastBuckets[transaction.InCurrency]); + var newNode = new FlowNode(transaction.DateTime, transaction.InAmount + oldNode.Amount, transaction.InCurrency, exchange.Id, Guid.Empty); + nodes.Add(newNode); + var link1 = new FlowLink(transaction.DateTime, oldNode.Amount, transaction.InCurrency, oldNode.Id, newNode.Id, exchange.Id); + var link2 = new FlowLink(transaction.DateTime, transaction.InAmount, transaction.InCurrency, sourceNode.Id, newNode.Id, exchange.Id, "In"); + links.Add(link1); + links.Add(link2); + lastBuckets[transaction.InCurrency] = newNode.Id; + + } + else + { + var sourceNode = nodes.Single(n => n.TransactionId == transaction.Id); + var node = new FlowNode(transaction.DateTime, transaction.InAmount, transaction.InCurrency, exchange.Id, transaction.Id); + nodes.Add(node); + + var link = new FlowLink(transaction.DateTime, transaction.InAmount, transaction.InCurrency, sourceNode.Id, node.Id, exchange.Id, "In"); + links.Add(link); + lastBuckets.Add(transaction.InCurrency, node.Id); + } + } + + break; + + case TransactionType.Trade: + if (lastBuckets.ContainsKey(transaction.BuyCurrency)) + { + // Buy Bucket already exists + var buyBucketNode = nodes.Single(n => n.Id == lastBuckets[transaction.BuyCurrency]); + var prevNode = nodes.Single(n => n.Id == lastBuckets[transaction.SellCurrency]); + var restNode = new FlowNode(transaction.DateTime, prevNode.Amount - transaction.SellAmount, transaction.SellCurrency, exchange.Id, transaction.Id); + var newBuyBucketNode = new FlowNode(transaction.DateTime, buyBucketNode.Amount + transaction.BuyAmount, buyBucketNode.Currency, exchange.Id, transaction.Id); + + // Calculate fee + if (transaction.FeeCurrency == prevNode.Currency) + { + restNode.Amount -= transaction.FeeAmount; + } + else if (transaction.FeeCurrency == newBuyBucketNode.Currency) + { + newBuyBucketNode.Amount -= transaction.FeeAmount; + } + else + { + throw new NotImplementedException("Transaction Fee could not be added"); + } + + var linkToNewBuyBucket = new FlowLink(transaction.DateTime, transaction.BuyAmount, transaction.BuyCurrency, prevNode.Id, newBuyBucketNode.Id, exchange.Id, $"Trade {Math.Round(transaction.BuyAmount, 2)} {transaction.BuyCurrency} for {Math.Round(transaction.SellAmount, 2)} {transaction.SellCurrency}"); + var linkToNewBucket = new FlowLink(transaction.DateTime, buyBucketNode.Amount, buyBucketNode.Currency, buyBucketNode.Id, newBuyBucketNode.Id, exchange.Id); + var linkToNewRestBucket = new FlowLink(transaction.DateTime, prevNode.Amount - transaction.SellAmount, transaction.SellCurrency, prevNode.Id, restNode.Id, exchange.Id); + + // Add data + nodes.Add(restNode); + nodes.Add(newBuyBucketNode); + links.Add(linkToNewBuyBucket); + links.Add(linkToNewBucket); + links.Add(linkToNewRestBucket); + + // Set new Buckets + lastBuckets[transaction.BuyCurrency] = newBuyBucketNode.Id; + lastBuckets[transaction.SellCurrency] = restNode.Id; + } + else + { + // Create new Buy Bucket + var prevNode = nodes.Single(n => n.Id == lastBuckets[transaction.SellCurrency]); + var buyNode = new FlowNode(transaction.DateTime, transaction.BuyAmount, transaction.BuyCurrency, exchange.Id, transaction.Id); + var sellAndRestNode = new FlowNode(transaction.DateTime, prevNode.Amount - transaction.SellAmount, transaction.SellCurrency, exchange.Id, transaction.Id); + + // Calculate fee + if (transaction.FeeCurrency == prevNode.Currency) + { + sellAndRestNode.Amount -= transaction.FeeAmount; + } + else if (transaction.FeeCurrency == buyNode.Currency) + { + buyNode.Amount -= transaction.FeeAmount; + } + else + { + throw new NotImplementedException("Transaction Fee could not be added"); + } + + + // Add Links + var buyLink = new FlowLink(transaction.DateTime, transaction.BuyAmount, transaction.BuyCurrency, prevNode.Id, buyNode.Id, exchange.Id, $"Trade {Math.Round(transaction.BuyAmount, 2)} {transaction.BuyCurrency} for {Math.Round(transaction.SellAmount, 2)} {transaction.SellCurrency}"); + var sellAndRestLink = new FlowLink(transaction.DateTime, prevNode.Amount - transaction.SellAmount, transaction.SellCurrency, prevNode.Id, sellAndRestNode.Id, exchange.Id); + + // Add to Dictionaries + links.Add(buyLink); + links.Add(sellAndRestLink); + nodes.Add(buyNode); + nodes.Add(sellAndRestNode); + + // Set new Buckets + lastBuckets[transaction.BuyCurrency] = buyNode.Id; + lastBuckets[transaction.SellCurrency] = sellAndRestNode.Id; + } + break; + + case TransactionType.Out: + var previousNode = nodes.Single(n => n.Id == lastBuckets[transaction.OutCurrency]); + + var outNode = nodes.Single(n => n.TransactionId == transaction.Id); + var nextNode = new FlowNode(transaction.DateTime, previousNode.Amount - outNode.Amount, transaction.OutCurrency, exchange.Id, Guid.Empty); + nodes.Add(nextNode); + + var linkPreviousOut = new FlowLink(transaction.DateTime, transaction.OutAmount, transaction.OutCurrency, previousNode.Id, outNode.Id, exchange.Id, "Out"); + var linkPreviousNext = new FlowLink(transaction.DateTime, nextNode.Amount, transaction.OutCurrency, previousNode.Id, nextNode.Id, exchange.Id); + links.Add(linkPreviousOut); + links.Add(linkPreviousNext); + lastBuckets[transaction.OutCurrency] = nextNode.Id; + + break; + + default: + throw new ArgumentException($"Transaction Type {transaction.Type} is unknown"); + } + } + + + allNodes.Add(exchange.Id, nodes); + allLinks.Add(exchange.Id, links); + } + + + + // Save Data + foreach (var allNode in allNodes) + { + foreach (var nodes in allNode.Value) + { + _context.FlowNodes.Add(nodes); + } + } + foreach (var allLink in allLinks) + { + foreach (var link in allLink.Value) + { + _context.FlowLinks.Add(link); + } + } + + _context.SaveChanges(); + } + class Fiat { public decimal Investments { get; set; } @@ -202,3 +394,6 @@ class Fiat } } + + + diff --git a/CryptoManager/ClientApp/app/app.shared.module.ts b/CryptoManager/ClientApp/app/app.shared.module.ts index 4821bad..c0ad50f 100644 --- a/CryptoManager/ClientApp/app/app.shared.module.ts +++ b/CryptoManager/ClientApp/app/app.shared.module.ts @@ -9,6 +9,9 @@ import { BrowserModule } from '@angular/platform-browser'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { DropdownModule, ButtonModule, DataTableModule, SharedModule, ConfirmDialogModule, ConfirmationService } from 'primeng/primeng'; +import { NgxGraphModule } from '@swimlane/ngx-graph'; +import { NgxChartsModule } from '@swimlane/ngx-charts'; + import { AppComponent } from './components/app/app.component'; import { NavMenuComponent } from './components/navmenu/navmenu.component'; @@ -17,49 +20,55 @@ import { ExchangesComponent } from './components/exchanges/exchanges.component'; import { TransactionsComponent } from './components/transactions/transactions.component'; import { FundsComponent } from './components/funds/funds.component'; import { InvestmentsComponent } from './components/investments/investments.component' +import { FlowComponent } from './components/flow/flow.component' import { CryptoApiClient } from './services/api-client'; @NgModule({ - declarations: [ - AppComponent, - NavMenuComponent, - HomeComponent, - ExchangesComponent, - TransactionsComponent, - FundsComponent, - InvestmentsComponent - ], - imports: [ - CommonModule, - HttpModule, - FormsModule, + declarations: [ + AppComponent, + NavMenuComponent, + HomeComponent, + ExchangesComponent, + TransactionsComponent, + FundsComponent, + InvestmentsComponent, + FlowComponent + ], + imports: [ + CommonModule, + HttpModule, + FormsModule, + + DropdownModule, + ButtonModule, + DataTableModule, + SharedModule, + ConfirmDialogModule, - DropdownModule, - ButtonModule, - DataTableModule, - SharedModule, - ConfirmDialogModule, + BrowserModule, + BrowserAnimationsModule, + HttpClientModule, - BrowserModule, - BrowserAnimationsModule, - HttpClientModule, + NgxChartsModule, + NgxGraphModule, - RouterModule.forRoot([ - { path: '', redirectTo: 'home', pathMatch: 'full' }, - { path: 'home', component: HomeComponent }, - { path: 'transactions', component: TransactionsComponent }, - { path: 'funds', component: FundsComponent }, - { path: 'investments', component: InvestmentsComponent }, - { path: 'exchanges', component: ExchangesComponent }, - { path: '**', redirectTo: 'home' } - ]) - ], - providers: [ - CryptoApiClient, + RouterModule.forRoot([ + { path: '', redirectTo: 'home', pathMatch: 'full' }, + { path: 'home', component: HomeComponent }, + { path: 'transactions', component: TransactionsComponent }, + { path: 'funds', component: FundsComponent }, + { path: 'investments', component: InvestmentsComponent }, + { path: 'exchanges', component: ExchangesComponent }, + { path: 'flow', component: FlowComponent }, + { path: '**', redirectTo: 'home' } + ]) + ], + providers: [ + CryptoApiClient, - ConfirmationService - ] + ConfirmationService + ] }) export class AppModuleShared { } diff --git a/CryptoManager/ClientApp/app/components/flow/flow.component.html b/CryptoManager/ClientApp/app/components/flow/flow.component.html new file mode 100644 index 0000000..336d5ef --- /dev/null +++ b/CryptoManager/ClientApp/app/components/flow/flow.component.html @@ -0,0 +1,57 @@ +

Flow

+ +
+ +
+ +
+ + + + + + + + + + + + {{node.label}} + + + + + + + + + + {{link.label}} + + + + + + +
diff --git a/CryptoManager/ClientApp/app/components/flow/flow.component.scss b/CryptoManager/ClientApp/app/components/flow/flow.component.scss new file mode 100644 index 0000000..e197108 --- /dev/null +++ b/CryptoManager/ClientApp/app/components/flow/flow.component.scss @@ -0,0 +1,3 @@ +.graph .edge .edge-label{ + fill: black !important; +} \ No newline at end of file diff --git a/CryptoManager/ClientApp/app/components/flow/flow.component.spec.ts b/CryptoManager/ClientApp/app/components/flow/flow.component.spec.ts new file mode 100644 index 0000000..c627c18 --- /dev/null +++ b/CryptoManager/ClientApp/app/components/flow/flow.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { FlowComponent } from './flow.component'; + +describe('FlowComponent', () => { + let component: FlowComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [FlowComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(FlowComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/CryptoManager/ClientApp/app/components/flow/flow.component.ts b/CryptoManager/ClientApp/app/components/flow/flow.component.ts new file mode 100644 index 0000000..3f5c16f --- /dev/null +++ b/CryptoManager/ClientApp/app/components/flow/flow.component.ts @@ -0,0 +1,133 @@ +import { Component, OnInit } from '@angular/core'; +import * as shape from 'd3-shape'; +import { CryptoApiClient, IExchangeDTO } from '../../services/api-client'; +import { SelectItem } from 'primeng/primeng'; + + +@Component({ + selector: 'app-flow', + templateUrl: './flow.component.html', + styleUrls: ['./flow.component.scss'] +}) + +export class FlowComponent implements OnInit { + + exchanges: SelectItem[] = [{ label: 'Select Exchange Plugin', value: null },]; + selectedExchange: IExchangeDTO; + + constructor(private apiClient: CryptoApiClient) { + const nodes: any[] = []; + const links: any[] = []; + this.hierarchialGraph = { nodes, links }; + + //this.apiClient.apiExchangesGet().subscribe(exchanges => { + // for (let exchange of exchanges) { + // this.exchanges.push(({ + // label: exchange.exchangeName, + // value: exchange.id + // })); + // } + // console.log(this.exchanges); + //}); + + + } + + + ngOnInit() { + this.apiClient.apiExchangesGet().subscribe(ex => { + for (let entry of ex) { + this.exchanges.push({ + label: entry.exchangeName, + value: entry + }); + } + }); + + + + + + if (!this.fitContainer) { + this.applyDimensions(); + } + } + + exchangeChanged() { + const nodes: any[] = []; + const links: any[] = []; + this.hierarchialGraph = { nodes, links }; + + if (this.selectedExchange == null) + return; + + + this.apiClient.apiFlowsNodesGet(String(this.selectedExchange.id)).subscribe(nodes => { + for (var i = 0; i < nodes.length; i++) { + var label = nodes[i].comment == null + ? nodes[i].amount + " " + nodes[i].currency + : nodes[i].comment + " " + nodes[i].amount + " " + nodes[i].currency; + + + this.hierarchialGraph.nodes.push({ + id: nodes[i].id, + label: label + }); + + } + this.hierarchialGraph.nodes = [...this.hierarchialGraph.nodes]; + + this.apiClient.apiFlowsLinksGet(String(this.selectedExchange.id)).subscribe(links => { + for (var i = 0; i < links.length; i++) { + + this.hierarchialGraph.links.push({ + source: links[i].flowNodeSource, + target: links[i].flowNodeTarget, + label: links[i].comment + }); + + } + + this.hierarchialGraph.links = [...this.hierarchialGraph.links]; + }); + }); + } + + applyDimensions() { + this.view = [this.width, this.height]; + } + + + + + colorScheme = { + name: 'picnic', + selectable: false, + group: 'Ordinal', + domain: [ + '#FAC51D', '#66BD6D', '#FAA026', '#29BB9C', '#E96B56', '#55ACD2', '#B7332F', '#2C83C9', '#9166B8', '#92E7E8' + ] + }; + schemeType: string = 'ordinal'; + + curveType: string = 'Linear'; + curve: any = shape.curveBundle.beta(1); + + + + hierarchialGraph: { links: any[], nodes: any[] }; + + + view: any[]; + width: number = 1200; + height: number = 1200; + fitContainer: boolean = true; + autoZoom: boolean = true; + + // options + showLegend = false; + orientation: string = 'TB'; // LR, RL, TB, BT + + +} + diff --git a/CryptoManager/ClientApp/app/components/navmenu/navmenu.component.html b/CryptoManager/ClientApp/app/components/navmenu/navmenu.component.html index 279cc2c..711f45e 100644 --- a/CryptoManager/ClientApp/app/components/navmenu/navmenu.component.html +++ b/CryptoManager/ClientApp/app/components/navmenu/navmenu.component.html @@ -42,6 +42,12 @@ +
  • + + Flow + +
  • +
  • Recalculate @@ -52,12 +58,12 @@ {{backgroundTasks}} Background tasks running {{backgroundTasks}} Background task running
  • - +
  • - +
  • diff --git a/CryptoManager/ClientApp/app/services/api-client.ts b/CryptoManager/ClientApp/app/services/api-client.ts index 22516ef..09db8e2 100644 --- a/CryptoManager/ClientApp/app/services/api-client.ts +++ b/CryptoManager/ClientApp/app/services/api-client.ts @@ -355,6 +355,126 @@ export class CryptoApiClient { return Observable.of(null); } + /** + * @return Success + */ + apiFlowsNodesGet(exchangeId: string): Observable { + let url_ = this.baseUrl + "/api/Flows/Nodes?"; + if (exchangeId === undefined || exchangeId === null) + throw new Error("The parameter 'exchangeId' must be defined and cannot be null."); + else + url_ += "exchangeId=" + encodeURIComponent("" + exchangeId) + "&"; + url_ = url_.replace(/[?&]$/, ""); + + let options_ : any = { + observe: "response", + responseType: "blob", + headers: new HttpHeaders({ + "Content-Type": "application/json", + "Accept": "application/json" + }) + }; + + return this.http.request("get", url_, options_).flatMap((response_ : any) => { + return this.processApiFlowsNodesGet(response_); + }).catch((response_: any) => { + if (response_ instanceof HttpResponseBase) { + try { + return this.processApiFlowsNodesGet(response_); + } catch (e) { + return >Observable.throw(e); + } + } else + return >Observable.throw(response_); + }); + } + + protected processApiFlowsNodesGet(response: HttpResponseBase): Observable { + const status = response.status; + const responseBlob = + response instanceof HttpResponse ? response.body : + response instanceof HttpErrorResponse ? response.error : undefined; + + let _headers: any = {}; if (response.headers) { for (let key of response.headers.keys()) { _headers[key] = response.headers.get(key); }}; + if (status === 200) { + return blobToText(responseBlob).flatMap(_responseText => { + let result200: any = null; + let resultData200 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver); + if (resultData200 && resultData200.constructor === Array) { + result200 = []; + for (let item of resultData200) + result200.push(FlowNodeDTO.fromJS(item)); + } + return Observable.of(result200); + }); + } else if (status !== 200 && status !== 204) { + return blobToText(responseBlob).flatMap(_responseText => { + return throwException("An unexpected server error occurred.", status, _responseText, _headers); + }); + } + return Observable.of(null); + } + + /** + * @return Success + */ + apiFlowsLinksGet(exchangeId: string): Observable { + let url_ = this.baseUrl + "/api/Flows/Links?"; + if (exchangeId === undefined || exchangeId === null) + throw new Error("The parameter 'exchangeId' must be defined and cannot be null."); + else + url_ += "exchangeId=" + encodeURIComponent("" + exchangeId) + "&"; + url_ = url_.replace(/[?&]$/, ""); + + let options_ : any = { + observe: "response", + responseType: "blob", + headers: new HttpHeaders({ + "Content-Type": "application/json", + "Accept": "application/json" + }) + }; + + return this.http.request("get", url_, options_).flatMap((response_ : any) => { + return this.processApiFlowsLinksGet(response_); + }).catch((response_: any) => { + if (response_ instanceof HttpResponseBase) { + try { + return this.processApiFlowsLinksGet(response_); + } catch (e) { + return >Observable.throw(e); + } + } else + return >Observable.throw(response_); + }); + } + + protected processApiFlowsLinksGet(response: HttpResponseBase): Observable { + const status = response.status; + const responseBlob = + response instanceof HttpResponse ? response.body : + response instanceof HttpErrorResponse ? response.error : undefined; + + let _headers: any = {}; if (response.headers) { for (let key of response.headers.keys()) { _headers[key] = response.headers.get(key); }}; + if (status === 200) { + return blobToText(responseBlob).flatMap(_responseText => { + let result200: any = null; + let resultData200 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver); + if (resultData200 && resultData200.constructor === Array) { + result200 = []; + for (let item of resultData200) + result200.push(FlowLink.fromJS(item)); + } + return Observable.of(result200); + }); + } else if (status !== 200 && status !== 204) { + return blobToText(responseBlob).flatMap(_responseText => { + return throwException("An unexpected server error occurred.", status, _responseText, _headers); + }); + } + return Observable.of(null); + } + /** * @return Success */ @@ -788,6 +908,128 @@ export interface IFiatDTO { exchangeId?: string | undefined; } +export class FlowNodeDTO implements IFlowNodeDTO { + id?: string | undefined; + dateTime?: Date | undefined; + amount?: number | undefined; + currency?: string | undefined; + exchangeId?: string | undefined; + comment?: string | undefined; + transactionId?: string | undefined; + + constructor(data?: IFlowNodeDTO) { + if (data) { + for (var property in data) { + if (data.hasOwnProperty(property)) + (this)[property] = (data)[property]; + } + } + } + + init(data?: any) { + if (data) { + this.id = data["id"]; + this.dateTime = data["dateTime"] ? new Date(data["dateTime"].toString()) : undefined; + this.amount = data["amount"]; + this.currency = data["currency"]; + this.exchangeId = data["exchangeId"]; + this.comment = data["comment"]; + this.transactionId = data["transactionId"]; + } + } + + static fromJS(data: any): FlowNodeDTO { + let result = new FlowNodeDTO(); + result.init(data); + return result; + } + + toJSON(data?: any) { + data = typeof data === 'object' ? data : {}; + data["id"] = this.id; + data["dateTime"] = this.dateTime ? this.dateTime.toISOString() : undefined; + data["amount"] = this.amount; + data["currency"] = this.currency; + data["exchangeId"] = this.exchangeId; + data["comment"] = this.comment; + data["transactionId"] = this.transactionId; + return data; + } +} + +export interface IFlowNodeDTO { + id?: string | undefined; + dateTime?: Date | undefined; + amount?: number | undefined; + currency?: string | undefined; + exchangeId?: string | undefined; + comment?: string | undefined; + transactionId?: string | undefined; +} + +export class FlowLink implements IFlowLink { + id?: string | undefined; + dateTime?: Date | undefined; + amount?: number | undefined; + currency?: string | undefined; + flowNodeSource?: string | undefined; + flowNodeTarget?: string | undefined; + comment?: string | undefined; + exchangeId?: string | undefined; + + constructor(data?: IFlowLink) { + if (data) { + for (var property in data) { + if (data.hasOwnProperty(property)) + (this)[property] = (data)[property]; + } + } + } + + init(data?: any) { + if (data) { + this.id = data["id"]; + this.dateTime = data["dateTime"] ? new Date(data["dateTime"].toString()) : undefined; + this.amount = data["amount"]; + this.currency = data["currency"]; + this.flowNodeSource = data["flowNodeSource"]; + this.flowNodeTarget = data["flowNodeTarget"]; + this.comment = data["comment"]; + this.exchangeId = data["exchangeId"]; + } + } + + static fromJS(data: any): FlowLink { + let result = new FlowLink(); + result.init(data); + return result; + } + + toJSON(data?: any) { + data = typeof data === 'object' ? data : {}; + data["id"] = this.id; + data["dateTime"] = this.dateTime ? this.dateTime.toISOString() : undefined; + data["amount"] = this.amount; + data["currency"] = this.currency; + data["flowNodeSource"] = this.flowNodeSource; + data["flowNodeTarget"] = this.flowNodeTarget; + data["comment"] = this.comment; + data["exchangeId"] = this.exchangeId; + return data; + } +} + +export interface IFlowLink { + id?: string | undefined; + dateTime?: Date | undefined; + amount?: number | undefined; + currency?: string | undefined; + flowNodeSource?: string | undefined; + flowNodeTarget?: string | undefined; + comment?: string | undefined; + exchangeId?: string | undefined; +} + export class FundDTO implements IFundDTO { id?: string | undefined; currency?: string | undefined; @@ -1184,6 +1426,7 @@ export enum ExchangeMetaExchangeId { _2 = 2, _3 = 3, _4 = 4, + _5 = 5, } export enum ExchangeDTOExchange { @@ -1191,6 +1434,7 @@ export enum ExchangeDTOExchange { _2 = 2, _3 = 3, _4 = 4, + _5 = 5, } export enum ExchangeId { @@ -1198,6 +1442,7 @@ export enum ExchangeId { _2 = 2, _3 = 3, _4 = 4, + _5 = 5, } export enum CryptoTransactionType { diff --git a/CryptoManager/Controllers/FlowsController.cs b/CryptoManager/Controllers/FlowsController.cs new file mode 100644 index 0000000..bd9c0ee --- /dev/null +++ b/CryptoManager/Controllers/FlowsController.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using AutoMapper; +using CryptoManager.Models; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Model.DbModels; +using Model.DTOs; + +namespace CryptoManager.Controllers +{ + [Produces("application/json")] + [Route("api/Flows")] + public class FlowsController : Controller + { + private readonly CryptoContext _context; + private readonly IMapper _mapper; + + public FlowsController(CryptoContext context, IMapper mapper) + { + _context = context; + _mapper = mapper; + } + + [HttpGet("Nodes")] + public async Task> GetNodes(Guid exchangeId) + { + return await Task.Run(() => _mapper.Map>(_context.FlowNodes.Where(f => f.ExchangeId == exchangeId))); + } + + [HttpGet("Links")] + public async Task> GetLinks(Guid exchangeId) + { + var res = await Task.Run(() => _mapper.Map>(_context.FlowLinks.Where(f => f.ExchangeId == exchangeId))); + return res; + } + } +} \ No newline at end of file diff --git a/CryptoManager/CryptoManager.csproj b/CryptoManager/CryptoManager.csproj index 1c66d5e..a0cb071 100644 --- a/CryptoManager/CryptoManager.csproj +++ b/CryptoManager/CryptoManager.csproj @@ -6,7 +6,7 @@ Latest false win-x64;osx-x64 - Exe + @@ -14,12 +14,12 @@ - + - - - - + + + + diff --git a/CryptoManager/MappingProfile.cs b/CryptoManager/MappingProfile.cs index 0ce29a6..0a0ff1b 100644 --- a/CryptoManager/MappingProfile.cs +++ b/CryptoManager/MappingProfile.cs @@ -17,6 +17,16 @@ public MappingProfile() CreateMap(); CreateMap(); + + CreateMap() + .ForMember(dto => dto.Id, + expression => expression.MapFrom(node => node.Id.ToString().Replace("-", string.Empty))); + + CreateMap() + .ForMember(dto => dto.FlowNodeSource, + expression => expression.MapFrom(link => link.FlowNodeSource.ToString().Replace("-", string.Empty))) + .ForMember(dto => dto.FlowNodeTarget, + expression => expression.MapFrom(link => link.FlowNodeTarget.ToString().Replace("-", string.Empty))); } } } diff --git a/CryptoManager/NLog.config b/CryptoManager/NLog.config index 8e7ffcb..f579242 100644 --- a/CryptoManager/NLog.config +++ b/CryptoManager/NLog.config @@ -3,12 +3,12 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" autoReload="true" internalLogLevel="info" - internalLogFile="c:\temp\internal-nlog.txt"> + internalLogFile="../log/internal-nlog.txt"> - + @@ -25,7 +25,7 @@ - + diff --git a/CryptoManager/Startup.cs b/CryptoManager/Startup.cs index 50c5888..c79226e 100644 --- a/CryptoManager/Startup.cs +++ b/CryptoManager/Startup.cs @@ -125,6 +125,7 @@ public void Configure(IApplicationBuilder app, IHostingEnvironment env) //BackgroundJob.Enqueue(i => i.ImportAll()); BackgroundJob.Enqueue(c => c.RecalculateAll()); + //BackgroundJob.Enqueue(c => c.CalculateFlow()); } } diff --git a/CryptoManager/package.json b/CryptoManager/package.json index cd39475..24b5bf8 100644 --- a/CryptoManager/package.json +++ b/CryptoManager/package.json @@ -20,6 +20,8 @@ "@angular/router": "^5.0.0", "@ngtools/webpack": "^1.0.0", "@types/chai": "^4.0.0", + "@types/d3": "^4.12.0", + "@types/d3-shape": "^1.2.2", "@types/jasmine": "^2.0.0", "@types/webpack-env": "^1.0.0", "angular2-router-loader": "0.3.5", @@ -63,6 +65,7 @@ "zone.js": "^0.8.0" }, "dependencies": { + "@swimlane/ngx-graph": "^4.0.2", "font-awesome": "^4.7.0", "font-awesome-webpack": "^0.0.5-beta.2", "primeng": "^5.0.2" diff --git a/CryptoManager/yarn.lock b/CryptoManager/yarn.lock index 971d3ca..60abcc0 100644 --- a/CryptoManager/yarn.lock +++ b/CryptoManager/yarn.lock @@ -184,10 +184,217 @@ dependencies: "@angular-devkit/core" "0.0.24" +"@swimlane/ngx-charts@^7.0.1": + version "7.0.1" + resolved "https://registry.yarnpkg.com/@swimlane/ngx-charts/-/ngx-charts-7.0.1.tgz#5b537150086f0d1776921148079e91437224fbef" + dependencies: + d3-array "^1.2.1" + d3-brush "^1.0.4" + d3-color "^1.0.3" + d3-force "^1.1.0" + d3-format "^1.2.0" + d3-hierarchy "^1.1.5" + d3-interpolate "^1.1.5" + d3-scale "^1.0.6" + d3-selection "^1.1.0" + d3-shape "^1.2.0" + d3-time-format "^2.1.0" + +"@swimlane/ngx-graph@^4.0.2": + version "4.0.2" + resolved "https://registry.yarnpkg.com/@swimlane/ngx-graph/-/ngx-graph-4.0.2.tgz#c6899aa3d937af39259fe568c7ea6682a9228e10" + dependencies: + "@swimlane/ngx-charts" "^7.0.1" + "@types/d3-transition" "^1.1.1" + d3-selection "^1.2.0" + d3-shape "^1.2.0" + d3-transition "^1.1.1" + dagre "^0.7.4" + "@types/chai@^4.0.0": version "4.1.0" resolved "https://registry.yarnpkg.com/@types/chai/-/chai-4.1.0.tgz#d9008fa4c06f6801f93396d121f0227cd4244ac6" +"@types/d3-array@*": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@types/d3-array/-/d3-array-1.2.1.tgz#e489605208d46a1c9d980d2e5772fa9c75d9ec65" + +"@types/d3-axis@*": + version "1.0.9" + resolved "https://registry.yarnpkg.com/@types/d3-axis/-/d3-axis-1.0.9.tgz#62ce7bc8d04354298cda57f3f1d1f856ad69b89a" + dependencies: + "@types/d3-selection" "*" + +"@types/d3-brush@*": + version "1.0.7" + resolved "https://registry.yarnpkg.com/@types/d3-brush/-/d3-brush-1.0.7.tgz#05c30440f4d537fd23f976b0e6c4ba223001ef45" + dependencies: + "@types/d3-selection" "*" + +"@types/d3-chord@*": + version "1.0.6" + resolved "https://registry.yarnpkg.com/@types/d3-chord/-/d3-chord-1.0.6.tgz#0589eb97a3191f4edaf17b7bde498462890ce1ec" + +"@types/d3-collection@*": + version "1.0.5" + resolved "https://registry.yarnpkg.com/@types/d3-collection/-/d3-collection-1.0.5.tgz#bb1f3aa97cdc8d881645541b9d6cf87edfee9bc3" + +"@types/d3-color@*": + version "1.0.5" + resolved "https://registry.yarnpkg.com/@types/d3-color/-/d3-color-1.0.5.tgz#cad755f0fc6de7b70fa6e5e08afa81ef4c2248de" + +"@types/d3-dispatch@*": + version "1.0.5" + resolved "https://registry.yarnpkg.com/@types/d3-dispatch/-/d3-dispatch-1.0.5.tgz#f1f9187b538ecb05157569d8dc2f70dfb04f1b52" + +"@types/d3-drag@*": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@types/d3-drag/-/d3-drag-1.2.0.tgz#5ee6279432c894f85cb72fcda911a959bae11952" + dependencies: + "@types/d3-selection" "*" + +"@types/d3-dsv@*": + version "1.0.31" + resolved "https://registry.yarnpkg.com/@types/d3-dsv/-/d3-dsv-1.0.31.tgz#468302f18ac44db2a3944086388d862503ab9c6c" + +"@types/d3-ease@*": + version "1.0.7" + resolved "https://registry.yarnpkg.com/@types/d3-ease/-/d3-ease-1.0.7.tgz#93a301868be9e15061f3d44343b1ab3f8acb6f09" + +"@types/d3-force@*": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@types/d3-force/-/d3-force-1.1.0.tgz#40925ca3512b63bd424f7c9685e1781b5b0a1d7e" + +"@types/d3-format@*": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@types/d3-format/-/d3-format-1.2.1.tgz#9435fb1771d2fbf6a858c93218f4097c9aa396c1" + +"@types/d3-geo@*": + version "1.9.4" + resolved "https://registry.yarnpkg.com/@types/d3-geo/-/d3-geo-1.9.4.tgz#9cfa573b6702e260b3fec127d88589ca9fc2de1d" + dependencies: + "@types/geojson" "*" + +"@types/d3-hierarchy@*": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@types/d3-hierarchy/-/d3-hierarchy-1.1.0.tgz#50f1ee052840638035cbdd4acab1fc3470905907" + +"@types/d3-interpolate@*": + version "1.1.6" + resolved "https://registry.yarnpkg.com/@types/d3-interpolate/-/d3-interpolate-1.1.6.tgz#64041b15c9c032c348da1b22baabc59fa4d16136" + dependencies: + "@types/d3-color" "*" + +"@types/d3-path@*": + version "1.0.6" + resolved "https://registry.yarnpkg.com/@types/d3-path/-/d3-path-1.0.6.tgz#c1a7d2dc07b295fdd1c84dabe4404df991b48693" + +"@types/d3-polygon@*": + version "1.0.5" + resolved "https://registry.yarnpkg.com/@types/d3-polygon/-/d3-polygon-1.0.5.tgz#35ad54ed84c39d7e9f1252b6535be600be6cace2" + +"@types/d3-quadtree@*": + version "1.0.5" + resolved "https://registry.yarnpkg.com/@types/d3-quadtree/-/d3-quadtree-1.0.5.tgz#1ce1e659eae4530df0cb127f297f1741a367a82e" + +"@types/d3-queue@*": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@types/d3-queue/-/d3-queue-3.0.5.tgz#3e4cbe2aff61db6a0b2b8c4800299e4ec6acc850" + +"@types/d3-random@*": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@types/d3-random/-/d3-random-1.1.0.tgz#2dd08f1159c70719270e4a7c834af85c8b88d2c3" + +"@types/d3-request@*": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@types/d3-request/-/d3-request-1.0.2.tgz#db9db8154f47816584706c6e6f702be66f22f4be" + dependencies: + "@types/d3-dsv" "*" + +"@types/d3-scale@*": + version "1.0.11" + resolved "https://registry.yarnpkg.com/@types/d3-scale/-/d3-scale-1.0.11.tgz#8d92f2d07d9f225596e551d0c2f8d1459571cebf" + dependencies: + "@types/d3-time" "*" + +"@types/d3-selection@*": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@types/d3-selection/-/d3-selection-1.2.0.tgz#f0a4cca0a0e4187c336c6712a82600cdcd24093f" + +"@types/d3-shape@*", "@types/d3-shape@^1.2.2": + version "1.2.2" + resolved "https://registry.yarnpkg.com/@types/d3-shape/-/d3-shape-1.2.2.tgz#f8dcdff7772a7ae37858bf04abd43848a78e590e" + dependencies: + "@types/d3-path" "*" + +"@types/d3-time-format@*": + version "2.1.0" + resolved "https://registry.yarnpkg.com/@types/d3-time-format/-/d3-time-format-2.1.0.tgz#011e0fb7937be34a9a8f580ae1e2f2f1336a8a22" + +"@types/d3-time@*": + version "1.0.7" + resolved "https://registry.yarnpkg.com/@types/d3-time/-/d3-time-1.0.7.tgz#4266d7c9be15fa81256a88d1d052d61cd8dc572c" + +"@types/d3-timer@*": + version "1.0.6" + resolved "https://registry.yarnpkg.com/@types/d3-timer/-/d3-timer-1.0.6.tgz#786d4e20731adf03af2c5df6c86fe29667fe429b" + +"@types/d3-transition@*", "@types/d3-transition@^1.1.1": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@types/d3-transition/-/d3-transition-1.1.1.tgz#c209fce6a966d6696356dd42b091a9c6cc79929f" + dependencies: + "@types/d3-selection" "*" + +"@types/d3-voronoi@*": + version "1.1.7" + resolved "https://registry.yarnpkg.com/@types/d3-voronoi/-/d3-voronoi-1.1.7.tgz#c0a145cf04395927e01706ff6c4ff835c97a8ece" + +"@types/d3-zoom@*": + version "1.7.0" + resolved "https://registry.yarnpkg.com/@types/d3-zoom/-/d3-zoom-1.7.0.tgz#1221bbf6434820f044c80b551c5519b817008961" + dependencies: + "@types/d3-interpolate" "*" + "@types/d3-selection" "*" + +"@types/d3@^4.12.0": + version "4.12.0" + resolved "https://registry.yarnpkg.com/@types/d3/-/d3-4.12.0.tgz#445ede4ab7707db1a011ef43b2bd187d21bdaffc" + dependencies: + "@types/d3-array" "*" + "@types/d3-axis" "*" + "@types/d3-brush" "*" + "@types/d3-chord" "*" + "@types/d3-collection" "*" + "@types/d3-color" "*" + "@types/d3-dispatch" "*" + "@types/d3-drag" "*" + "@types/d3-dsv" "*" + "@types/d3-ease" "*" + "@types/d3-force" "*" + "@types/d3-format" "*" + "@types/d3-geo" "*" + "@types/d3-hierarchy" "*" + "@types/d3-interpolate" "*" + "@types/d3-path" "*" + "@types/d3-polygon" "*" + "@types/d3-quadtree" "*" + "@types/d3-queue" "*" + "@types/d3-random" "*" + "@types/d3-request" "*" + "@types/d3-scale" "*" + "@types/d3-selection" "*" + "@types/d3-shape" "*" + "@types/d3-time" "*" + "@types/d3-time-format" "*" + "@types/d3-timer" "*" + "@types/d3-transition" "*" + "@types/d3-voronoi" "*" + "@types/d3-zoom" "*" + +"@types/geojson@*": + version "7946.0.1" + resolved "https://registry.yarnpkg.com/@types/geojson/-/geojson-7946.0.1.tgz#1fc41280e42f08f0d568401a556bc97c34f5262e" + "@types/jasmine@^2.0.0": version "2.8.3" resolved "https://registry.yarnpkg.com/@types/jasmine/-/jasmine-2.8.3.tgz#f910edc67d69393d562d10f8f3d205ea3f3306bf" @@ -1544,12 +1751,134 @@ cyclist@~0.2.2: version "0.2.2" resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-0.2.2.tgz#1b33792e11e914a2fd6d6ed6447464444e5fa640" +d3-array@^1.2.0, d3-array@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/d3-array/-/d3-array-1.2.1.tgz#d1ca33de2f6ac31efadb8e050a021d7e2396d5dc" + +d3-brush@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/d3-brush/-/d3-brush-1.0.4.tgz#00c2f238019f24f6c0a194a26d41a1530ffe7bc4" + dependencies: + d3-dispatch "1" + d3-drag "1" + d3-interpolate "1" + d3-selection "1" + d3-transition "1" + +d3-collection@1: + version "1.0.4" + resolved "https://registry.yarnpkg.com/d3-collection/-/d3-collection-1.0.4.tgz#342dfd12837c90974f33f1cc0a785aea570dcdc2" + +d3-color@1, d3-color@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/d3-color/-/d3-color-1.0.3.tgz#bc7643fca8e53a8347e2fbdaffa236796b58509b" + +d3-dispatch@1: + version "1.0.3" + resolved "https://registry.yarnpkg.com/d3-dispatch/-/d3-dispatch-1.0.3.tgz#46e1491eaa9b58c358fce5be4e8bed626e7871f8" + +d3-drag@1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/d3-drag/-/d3-drag-1.2.1.tgz#df8dd4c502fb490fc7462046a8ad98a5c479282d" + dependencies: + d3-dispatch "1" + d3-selection "1" + +d3-ease@1: + version "1.0.3" + resolved "https://registry.yarnpkg.com/d3-ease/-/d3-ease-1.0.3.tgz#68bfbc349338a380c44d8acc4fbc3304aa2d8c0e" + +d3-force@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/d3-force/-/d3-force-1.1.0.tgz#cebf3c694f1078fcc3d4daf8e567b2fbd70d4ea3" + dependencies: + d3-collection "1" + d3-dispatch "1" + d3-quadtree "1" + d3-timer "1" + +d3-format@1, d3-format@^1.2.0: + version "1.2.2" + resolved "https://registry.yarnpkg.com/d3-format/-/d3-format-1.2.2.tgz#1a39c479c8a57fe5051b2e67a3bee27061a74e7a" + +d3-hierarchy@^1.1.5: + version "1.1.5" + resolved "https://registry.yarnpkg.com/d3-hierarchy/-/d3-hierarchy-1.1.5.tgz#a1c845c42f84a206bcf1c01c01098ea4ddaa7a26" + +d3-interpolate@1, d3-interpolate@^1.1.5: + version "1.1.6" + resolved "https://registry.yarnpkg.com/d3-interpolate/-/d3-interpolate-1.1.6.tgz#2cf395ae2381804df08aa1bf766b7f97b5f68fb6" + dependencies: + d3-color "1" + +d3-path@1: + version "1.0.5" + resolved "https://registry.yarnpkg.com/d3-path/-/d3-path-1.0.5.tgz#241eb1849bd9e9e8021c0d0a799f8a0e8e441764" + +d3-quadtree@1: + version "1.0.3" + resolved "https://registry.yarnpkg.com/d3-quadtree/-/d3-quadtree-1.0.3.tgz#ac7987e3e23fe805a990f28e1b50d38fcb822438" + +d3-scale@^1.0.6: + version "1.0.7" + resolved "https://registry.yarnpkg.com/d3-scale/-/d3-scale-1.0.7.tgz#fa90324b3ea8a776422bd0472afab0b252a0945d" + dependencies: + d3-array "^1.2.0" + d3-collection "1" + d3-color "1" + d3-format "1" + d3-interpolate "1" + d3-time "1" + d3-time-format "2" + +d3-selection@1, d3-selection@^1.1.0, d3-selection@^1.2.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/d3-selection/-/d3-selection-1.3.0.tgz#d53772382d3dc4f7507bfb28bcd2d6aed2a0ad6d" + +d3-shape@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/d3-shape/-/d3-shape-1.2.0.tgz#45d01538f064bafd05ea3d6d2cb748fd8c41f777" + dependencies: + d3-path "1" + +d3-time-format@2, d3-time-format@^2.1.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/d3-time-format/-/d3-time-format-2.1.1.tgz#85b7cdfbc9ffca187f14d3c456ffda268081bb31" + dependencies: + d3-time "1" + +d3-time@1: + version "1.0.8" + resolved "https://registry.yarnpkg.com/d3-time/-/d3-time-1.0.8.tgz#dbd2d6007bf416fe67a76d17947b784bffea1e84" + +d3-timer@1: + version "1.0.7" + resolved "https://registry.yarnpkg.com/d3-timer/-/d3-timer-1.0.7.tgz#df9650ca587f6c96607ff4e60cc38229e8dd8531" + +d3-transition@1, d3-transition@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/d3-transition/-/d3-transition-1.1.1.tgz#d8ef89c3b848735b060e54a39b32aaebaa421039" + dependencies: + d3-color "1" + d3-dispatch "1" + d3-ease "1" + d3-interpolate "1" + d3-selection "^1.1.0" + d3-timer "1" + d@1: version "1.0.0" resolved "https://registry.yarnpkg.com/d/-/d-1.0.0.tgz#754bb5bfe55451da69a58b94d45f4c5b0462d58f" dependencies: es5-ext "^0.10.9" +dagre@^0.7.4: + version "0.7.4" + resolved "https://registry.yarnpkg.com/dagre/-/dagre-0.7.4.tgz#de72f0e74a550ce11ce638f0a136fed712398022" + dependencies: + graphlib "^1.0.5" + lodash "^3.10.0" + dashdash@^1.12.0: version "1.14.1" resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0" @@ -2630,6 +2959,12 @@ graceful-fs@^4.1.11, graceful-fs@^4.1.2, graceful-fs@^4.1.6: version "4.1.11" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.11.tgz#0e8bdfe4d1ddb8854d64e04ea7c00e2a026e5658" +graphlib@^1.0.5: + version "1.0.7" + resolved "https://registry.yarnpkg.com/graphlib/-/graphlib-1.0.7.tgz#0cab2df0ffe6abe070b2625bfa1edb6ec967b8b1" + dependencies: + lodash "^3.10.0" + handle-thing@^1.2.5: version "1.2.5" resolved "https://registry.yarnpkg.com/handle-thing/-/handle-thing-1.2.5.tgz#fd7aad726bf1a5fd16dfc29b2f7a6601d27139c4" @@ -3630,7 +3965,7 @@ lodash.uniq@^4.5.0: version "4.5.0" resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" -lodash@^3.8.0: +lodash@^3.10.0, lodash@^3.8.0: version "3.10.1" resolved "https://registry.yarnpkg.com/lodash/-/lodash-3.10.1.tgz#5bf45e8e49ba4189e17d482789dfd15bd140b7b6" diff --git a/Model/CryptoContext.cs b/Model/CryptoContext.cs index 4ffb3ae..80e8b91 100644 --- a/Model/CryptoContext.cs +++ b/Model/CryptoContext.cs @@ -22,5 +22,7 @@ protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) public DbSet Exchanges { get; set; } public DbSet Funds { get; set; } public DbSet FiatBalances{ get; set; } + public DbSet FlowNodes { get; set; } + public DbSet FlowLinks { get; set; } } } diff --git a/Model/DTOs/FlowLinkDTO.cs b/Model/DTOs/FlowLinkDTO.cs new file mode 100644 index 0000000..08e3551 --- /dev/null +++ b/Model/DTOs/FlowLinkDTO.cs @@ -0,0 +1,14 @@ +using Model.DbModels; + +namespace Model.DTOs +{ + public class FlowLinkDTO : FlowLink + { + #region Properties + + public new string FlowNodeSource { get; set; } + public new string FlowNodeTarget { get; set; } + + #endregion + } +} diff --git a/Model/DTOs/FlowNodeDTO.cs b/Model/DTOs/FlowNodeDTO.cs new file mode 100644 index 0000000..3fe4940 --- /dev/null +++ b/Model/DTOs/FlowNodeDTO.cs @@ -0,0 +1,13 @@ +using Model.DbModels; + +namespace Model.DTOs +{ + public class FlowNodeDTO : FlowNode + { + #region Properties + + public new string Id { get; set; } + + #endregion + } +} diff --git a/Model/DbModels/FlowLink.cs b/Model/DbModels/FlowLink.cs new file mode 100644 index 0000000..d4b12fd --- /dev/null +++ b/Model/DbModels/FlowLink.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Model.DbModels +{ + public class FlowLink + { + public FlowLink(DateTime dateTime, decimal amount, string currency, Guid flowNodeSource, Guid flowNodeTarget, Guid exchangeId, string comment = null) + { + DateTime = dateTime; + Amount = amount; + Currency = currency; + FlowNodeSource = flowNodeSource; + FlowNodeTarget = flowNodeTarget; + Comment = comment; + ExchangeId = exchangeId; + Id = Guid.NewGuid(); + } + public FlowLink() + { + } + public Guid Id { get; set; } + public DateTime DateTime { get; set; } + public decimal Amount { get; set; } + public string Currency { get; set; } + public Guid FlowNodeSource { get; set; } + public Guid FlowNodeTarget { get; set; } + public string Comment { get; set; } + public Guid ExchangeId { get; set; } + } +} diff --git a/Model/DbModels/FlowNode.cs b/Model/DbModels/FlowNode.cs new file mode 100644 index 0000000..1611bb1 --- /dev/null +++ b/Model/DbModels/FlowNode.cs @@ -0,0 +1,33 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Model.DbModels +{ + public class FlowNode + { + public FlowNode() + { + } + public FlowNode(DateTime dateTime, decimal amount, string currency, Guid exchangeId, Guid transactionId, string comment = null) + { + DateTime = dateTime; + Amount = amount; + Currency = currency; + ExchangeId = exchangeId; + TransactionId = transactionId; + Comment = comment; + TransactionId = transactionId; + + Id = Guid.NewGuid(); + } + + public Guid Id { get; set; } + public DateTime DateTime { get; set; } + public decimal Amount { get; set; } + public string Currency { get; set; } + public Guid ExchangeId { get; set; } + public string Comment { get; set; } + public Guid TransactionId { get; set; } + } +} diff --git a/Model/Migrations/20180109221301_AddFlows.Designer.cs b/Model/Migrations/20180109221301_AddFlows.Designer.cs new file mode 100644 index 0000000..bb4961e --- /dev/null +++ b/Model/Migrations/20180109221301_AddFlows.Designer.cs @@ -0,0 +1,168 @@ +// +using CryptoManager.Models; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage; +using Microsoft.EntityFrameworkCore.Storage.Internal; +using Model.Enums; +using System; + +namespace Model.Migrations +{ + [DbContext(typeof(CryptoContext))] + [Migration("20180109221301_AddFlows")] + partial class AddFlows + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.0.1-rtm-125"); + + modelBuilder.Entity("Model.DbModels.CryptoTransaction", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BuyAmount"); + + b.Property("BuyCurrency"); + + b.Property("BuyFiatAmount"); + + b.Property("BuyFiatRate"); + + b.Property("Comment"); + + b.Property("DateTime"); + + b.Property("ExchangeId"); + + b.Property("FeeAmount"); + + b.Property("FeeCurrency"); + + b.Property("FromAddress"); + + b.Property("InAmount"); + + b.Property("InCurrency"); + + b.Property("OutAmount"); + + b.Property("OutCurrency"); + + b.Property("Rate"); + + b.Property("SellAmount"); + + b.Property("SellCurrency"); + + b.Property("ToAddress"); + + b.Property("TransactionHash"); + + b.Property("TransactionKey"); + + b.Property("Type"); + + b.HasKey("Id"); + + b.ToTable("Transactions"); + }); + + modelBuilder.Entity("Model.DbModels.Exchange", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Comment"); + + b.Property("ExchangeId"); + + b.Property("PrivateKey"); + + b.Property("PublicKey"); + + b.HasKey("Id"); + + b.ToTable("Exchanges"); + }); + + modelBuilder.Entity("Model.DbModels.FiatBalance", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Currency"); + + b.Property("ExchangeId"); + + b.Property("Invested"); + + b.Property("Payout"); + + b.HasKey("Id"); + + b.ToTable("FiatBalances"); + }); + + modelBuilder.Entity("Model.DbModels.Flow", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Amount"); + + b.Property("Currency"); + + b.Property("DateTime"); + + b.Property("FlowId"); + + b.HasKey("Id"); + + b.HasIndex("FlowId"); + + b.ToTable("Flows"); + }); + + modelBuilder.Entity("Model.DbModels.Fund", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Amount"); + + b.Property("Currency"); + + b.Property("ExchangeId"); + + b.HasKey("Id"); + + b.ToTable("Funds"); + }); + + modelBuilder.Entity("Model.DbModels.Setting", b => + { + b.Property("Key"); + + b.Property("Value"); + + b.HasKey("Key"); + + b.ToTable("Settings"); + }); + + modelBuilder.Entity("Model.DbModels.Flow", b => + { + b.HasOne("Model.DbModels.Flow") + .WithMany("Parents") + .HasForeignKey("FlowId"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Model/Migrations/20180109221301_AddFlows.cs b/Model/Migrations/20180109221301_AddFlows.cs new file mode 100644 index 0000000..dd769ea --- /dev/null +++ b/Model/Migrations/20180109221301_AddFlows.cs @@ -0,0 +1,44 @@ +using Microsoft.EntityFrameworkCore.Migrations; +using System; +using System.Collections.Generic; + +namespace Model.Migrations +{ + public partial class AddFlows : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "Flows", + columns: table => new + { + Id = table.Column(nullable: false), + Amount = table.Column(nullable: false), + Currency = table.Column(nullable: true), + DateTime = table.Column(nullable: false), + FlowId = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Flows", x => x.Id); + table.ForeignKey( + name: "FK_Flows_Flows_FlowId", + column: x => x.FlowId, + principalTable: "Flows", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateIndex( + name: "IX_Flows_FlowId", + table: "Flows", + column: "FlowId"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "Flows"); + } + } +} diff --git a/Model/Migrations/20180109223036_AddFlowMeta.Designer.cs b/Model/Migrations/20180109223036_AddFlowMeta.Designer.cs new file mode 100644 index 0000000..aa74ff3 --- /dev/null +++ b/Model/Migrations/20180109223036_AddFlowMeta.Designer.cs @@ -0,0 +1,172 @@ +// +using CryptoManager.Models; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage; +using Microsoft.EntityFrameworkCore.Storage.Internal; +using Model.Enums; +using System; + +namespace Model.Migrations +{ + [DbContext(typeof(CryptoContext))] + [Migration("20180109223036_AddFlowMeta")] + partial class AddFlowMeta + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.0.1-rtm-125"); + + modelBuilder.Entity("Model.DbModels.CryptoTransaction", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BuyAmount"); + + b.Property("BuyCurrency"); + + b.Property("BuyFiatAmount"); + + b.Property("BuyFiatRate"); + + b.Property("Comment"); + + b.Property("DateTime"); + + b.Property("ExchangeId"); + + b.Property("FeeAmount"); + + b.Property("FeeCurrency"); + + b.Property("FromAddress"); + + b.Property("InAmount"); + + b.Property("InCurrency"); + + b.Property("OutAmount"); + + b.Property("OutCurrency"); + + b.Property("Rate"); + + b.Property("SellAmount"); + + b.Property("SellCurrency"); + + b.Property("ToAddress"); + + b.Property("TransactionHash"); + + b.Property("TransactionKey"); + + b.Property("Type"); + + b.HasKey("Id"); + + b.ToTable("Transactions"); + }); + + modelBuilder.Entity("Model.DbModels.Exchange", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Comment"); + + b.Property("ExchangeId"); + + b.Property("PrivateKey"); + + b.Property("PublicKey"); + + b.HasKey("Id"); + + b.ToTable("Exchanges"); + }); + + modelBuilder.Entity("Model.DbModels.FiatBalance", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Currency"); + + b.Property("ExchangeId"); + + b.Property("Invested"); + + b.Property("Payout"); + + b.HasKey("Id"); + + b.ToTable("FiatBalances"); + }); + + modelBuilder.Entity("Model.DbModels.Flow", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Amount"); + + b.Property("Currency"); + + b.Property("DateTime"); + + b.Property("ExchangeId"); + + b.Property("FlowId"); + + b.Property("TransactionId"); + + b.HasKey("Id"); + + b.HasIndex("FlowId"); + + b.ToTable("Flows"); + }); + + modelBuilder.Entity("Model.DbModels.Fund", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Amount"); + + b.Property("Currency"); + + b.Property("ExchangeId"); + + b.HasKey("Id"); + + b.ToTable("Funds"); + }); + + modelBuilder.Entity("Model.DbModels.Setting", b => + { + b.Property("Key"); + + b.Property("Value"); + + b.HasKey("Key"); + + b.ToTable("Settings"); + }); + + modelBuilder.Entity("Model.DbModels.Flow", b => + { + b.HasOne("Model.DbModels.Flow") + .WithMany("Parents") + .HasForeignKey("FlowId"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Model/Migrations/20180109223036_AddFlowMeta.cs b/Model/Migrations/20180109223036_AddFlowMeta.cs new file mode 100644 index 0000000..6f4d370 --- /dev/null +++ b/Model/Migrations/20180109223036_AddFlowMeta.cs @@ -0,0 +1,35 @@ +using Microsoft.EntityFrameworkCore.Migrations; +using System; +using System.Collections.Generic; + +namespace Model.Migrations +{ + public partial class AddFlowMeta : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "ExchangeId", + table: "Flows", + nullable: false, + defaultValue: new Guid("00000000-0000-0000-0000-000000000000")); + + migrationBuilder.AddColumn( + name: "TransactionId", + table: "Flows", + nullable: false, + defaultValue: new Guid("00000000-0000-0000-0000-000000000000")); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "ExchangeId", + table: "Flows"); + + migrationBuilder.DropColumn( + name: "TransactionId", + table: "Flows"); + } + } +} diff --git a/Model/Migrations/20180207154831_NewFlowStructure.Designer.cs b/Model/Migrations/20180207154831_NewFlowStructure.Designer.cs new file mode 100644 index 0000000..5fd09f0 --- /dev/null +++ b/Model/Migrations/20180207154831_NewFlowStructure.Designer.cs @@ -0,0 +1,183 @@ +// +using CryptoManager.Models; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage; +using Microsoft.EntityFrameworkCore.Storage.Internal; +using Model.Enums; +using System; + +namespace Model.Migrations +{ + [DbContext(typeof(CryptoContext))] + [Migration("20180207154831_NewFlowStructure")] + partial class NewFlowStructure + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.0.1-rtm-125"); + + modelBuilder.Entity("Model.DbModels.CryptoTransaction", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BuyAmount"); + + b.Property("BuyCurrency"); + + b.Property("BuyFiatAmount"); + + b.Property("BuyFiatRate"); + + b.Property("Comment"); + + b.Property("DateTime"); + + b.Property("ExchangeId"); + + b.Property("FeeAmount"); + + b.Property("FeeCurrency"); + + b.Property("FromAddress"); + + b.Property("InAmount"); + + b.Property("InCurrency"); + + b.Property("OutAmount"); + + b.Property("OutCurrency"); + + b.Property("Rate"); + + b.Property("SellAmount"); + + b.Property("SellCurrency"); + + b.Property("ToAddress"); + + b.Property("TransactionHash"); + + b.Property("TransactionKey"); + + b.Property("Type"); + + b.HasKey("Id"); + + b.ToTable("Transactions"); + }); + + modelBuilder.Entity("Model.DbModels.Exchange", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Comment"); + + b.Property("ExchangeId"); + + b.Property("Passphrase"); + + b.Property("PrivateKey"); + + b.Property("PublicKey"); + + b.HasKey("Id"); + + b.ToTable("Exchanges"); + }); + + modelBuilder.Entity("Model.DbModels.FiatBalance", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Currency"); + + b.Property("ExchangeId"); + + b.Property("Invested"); + + b.Property("Payout"); + + b.HasKey("Id"); + + b.ToTable("FiatBalances"); + }); + + modelBuilder.Entity("Model.DbModels.FlowLink", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Amount"); + + b.Property("Currency"); + + b.Property("DateTime"); + + b.Property("FlowNodeSource"); + + b.Property("FlowNodeTarget"); + + b.HasKey("Id"); + + b.ToTable("FlowLinks"); + }); + + modelBuilder.Entity("Model.DbModels.FlowNode", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Amount"); + + b.Property("Comment"); + + b.Property("Currency"); + + b.Property("DateTime"); + + b.Property("ExchangeId"); + + b.HasKey("Id"); + + b.ToTable("FlowNodes"); + }); + + modelBuilder.Entity("Model.DbModels.Fund", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Amount"); + + b.Property("Currency"); + + b.Property("ExchangeId"); + + b.HasKey("Id"); + + b.ToTable("Funds"); + }); + + modelBuilder.Entity("Model.DbModels.Setting", b => + { + b.Property("Key"); + + b.Property("Value"); + + b.HasKey("Key"); + + b.ToTable("Settings"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Model/Migrations/20180207154831_NewFlowStructure.cs b/Model/Migrations/20180207154831_NewFlowStructure.cs new file mode 100644 index 0000000..b810a27 --- /dev/null +++ b/Model/Migrations/20180207154831_NewFlowStructure.cs @@ -0,0 +1,84 @@ +using Microsoft.EntityFrameworkCore.Migrations; +using System; +using System.Collections.Generic; + +namespace Model.Migrations +{ + public partial class NewFlowStructure : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "Flows"); + + migrationBuilder.CreateTable( + name: "FlowLinks", + columns: table => new + { + Id = table.Column(nullable: false), + Amount = table.Column(nullable: false), + Currency = table.Column(nullable: true), + DateTime = table.Column(nullable: false), + FlowNodeSource = table.Column(nullable: false), + FlowNodeTarget = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_FlowLinks", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "FlowNodes", + columns: table => new + { + Id = table.Column(nullable: false), + Amount = table.Column(nullable: false), + Comment = table.Column(nullable: true), + Currency = table.Column(nullable: true), + DateTime = table.Column(nullable: false), + ExchangeId = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_FlowNodes", x => x.Id); + }); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "FlowLinks"); + + migrationBuilder.DropTable( + name: "FlowNodes"); + + migrationBuilder.CreateTable( + name: "Flows", + columns: table => new + { + Id = table.Column(nullable: false), + Amount = table.Column(nullable: false), + Currency = table.Column(nullable: true), + DateTime = table.Column(nullable: false), + ExchangeId = table.Column(nullable: false), + FlowId = table.Column(nullable: true), + TransactionId = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Flows", x => x.Id); + table.ForeignKey( + name: "FK_Flows_Flows_FlowId", + column: x => x.FlowId, + principalTable: "Flows", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateIndex( + name: "IX_Flows_FlowId", + table: "Flows", + column: "FlowId"); + } + } +} diff --git a/Model/Migrations/20180208081649_AddTransactionIdToFlowNodes.Designer.cs b/Model/Migrations/20180208081649_AddTransactionIdToFlowNodes.Designer.cs new file mode 100644 index 0000000..da19977 --- /dev/null +++ b/Model/Migrations/20180208081649_AddTransactionIdToFlowNodes.Designer.cs @@ -0,0 +1,185 @@ +// +using CryptoManager.Models; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage; +using Microsoft.EntityFrameworkCore.Storage.Internal; +using Model.Enums; +using System; + +namespace Model.Migrations +{ + [DbContext(typeof(CryptoContext))] + [Migration("20180208081649_AddTransactionIdToFlowNodes")] + partial class AddTransactionIdToFlowNodes + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.0.1-rtm-125"); + + modelBuilder.Entity("Model.DbModels.CryptoTransaction", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BuyAmount"); + + b.Property("BuyCurrency"); + + b.Property("BuyFiatAmount"); + + b.Property("BuyFiatRate"); + + b.Property("Comment"); + + b.Property("DateTime"); + + b.Property("ExchangeId"); + + b.Property("FeeAmount"); + + b.Property("FeeCurrency"); + + b.Property("FromAddress"); + + b.Property("InAmount"); + + b.Property("InCurrency"); + + b.Property("OutAmount"); + + b.Property("OutCurrency"); + + b.Property("Rate"); + + b.Property("SellAmount"); + + b.Property("SellCurrency"); + + b.Property("ToAddress"); + + b.Property("TransactionHash"); + + b.Property("TransactionKey"); + + b.Property("Type"); + + b.HasKey("Id"); + + b.ToTable("Transactions"); + }); + + modelBuilder.Entity("Model.DbModels.Exchange", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Comment"); + + b.Property("ExchangeId"); + + b.Property("Passphrase"); + + b.Property("PrivateKey"); + + b.Property("PublicKey"); + + b.HasKey("Id"); + + b.ToTable("Exchanges"); + }); + + modelBuilder.Entity("Model.DbModels.FiatBalance", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Currency"); + + b.Property("ExchangeId"); + + b.Property("Invested"); + + b.Property("Payout"); + + b.HasKey("Id"); + + b.ToTable("FiatBalances"); + }); + + modelBuilder.Entity("Model.DbModels.FlowLink", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Amount"); + + b.Property("Currency"); + + b.Property("DateTime"); + + b.Property("FlowNodeSource"); + + b.Property("FlowNodeTarget"); + + b.HasKey("Id"); + + b.ToTable("FlowLinks"); + }); + + modelBuilder.Entity("Model.DbModels.FlowNode", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Amount"); + + b.Property("Comment"); + + b.Property("Currency"); + + b.Property("DateTime"); + + b.Property("ExchangeId"); + + b.Property("TransactionId"); + + b.HasKey("Id"); + + b.ToTable("FlowNodes"); + }); + + modelBuilder.Entity("Model.DbModels.Fund", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Amount"); + + b.Property("Currency"); + + b.Property("ExchangeId"); + + b.HasKey("Id"); + + b.ToTable("Funds"); + }); + + modelBuilder.Entity("Model.DbModels.Setting", b => + { + b.Property("Key"); + + b.Property("Value"); + + b.HasKey("Key"); + + b.ToTable("Settings"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Model/Migrations/20180208081649_AddTransactionIdToFlowNodes.cs b/Model/Migrations/20180208081649_AddTransactionIdToFlowNodes.cs new file mode 100644 index 0000000..34ee706 --- /dev/null +++ b/Model/Migrations/20180208081649_AddTransactionIdToFlowNodes.cs @@ -0,0 +1,25 @@ +using Microsoft.EntityFrameworkCore.Migrations; +using System; +using System.Collections.Generic; + +namespace Model.Migrations +{ + public partial class AddTransactionIdToFlowNodes : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "TransactionId", + table: "FlowNodes", + nullable: false, + defaultValue: new Guid("00000000-0000-0000-0000-000000000000")); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "TransactionId", + table: "FlowNodes"); + } + } +} diff --git a/Model/Migrations/20180208101403_AddLinkComment.Designer.cs b/Model/Migrations/20180208101403_AddLinkComment.Designer.cs new file mode 100644 index 0000000..d3c1224 --- /dev/null +++ b/Model/Migrations/20180208101403_AddLinkComment.Designer.cs @@ -0,0 +1,187 @@ +// +using CryptoManager.Models; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage; +using Microsoft.EntityFrameworkCore.Storage.Internal; +using Model.Enums; +using System; + +namespace Model.Migrations +{ + [DbContext(typeof(CryptoContext))] + [Migration("20180208101403_AddLinkComment")] + partial class AddLinkComment + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.0.1-rtm-125"); + + modelBuilder.Entity("Model.DbModels.CryptoTransaction", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BuyAmount"); + + b.Property("BuyCurrency"); + + b.Property("BuyFiatAmount"); + + b.Property("BuyFiatRate"); + + b.Property("Comment"); + + b.Property("DateTime"); + + b.Property("ExchangeId"); + + b.Property("FeeAmount"); + + b.Property("FeeCurrency"); + + b.Property("FromAddress"); + + b.Property("InAmount"); + + b.Property("InCurrency"); + + b.Property("OutAmount"); + + b.Property("OutCurrency"); + + b.Property("Rate"); + + b.Property("SellAmount"); + + b.Property("SellCurrency"); + + b.Property("ToAddress"); + + b.Property("TransactionHash"); + + b.Property("TransactionKey"); + + b.Property("Type"); + + b.HasKey("Id"); + + b.ToTable("Transactions"); + }); + + modelBuilder.Entity("Model.DbModels.Exchange", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Comment"); + + b.Property("ExchangeId"); + + b.Property("Passphrase"); + + b.Property("PrivateKey"); + + b.Property("PublicKey"); + + b.HasKey("Id"); + + b.ToTable("Exchanges"); + }); + + modelBuilder.Entity("Model.DbModels.FiatBalance", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Currency"); + + b.Property("ExchangeId"); + + b.Property("Invested"); + + b.Property("Payout"); + + b.HasKey("Id"); + + b.ToTable("FiatBalances"); + }); + + modelBuilder.Entity("Model.DbModels.FlowLink", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Amount"); + + b.Property("Comment"); + + b.Property("Currency"); + + b.Property("DateTime"); + + b.Property("FlowNodeSource"); + + b.Property("FlowNodeTarget"); + + b.HasKey("Id"); + + b.ToTable("FlowLinks"); + }); + + modelBuilder.Entity("Model.DbModels.FlowNode", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Amount"); + + b.Property("Comment"); + + b.Property("Currency"); + + b.Property("DateTime"); + + b.Property("ExchangeId"); + + b.Property("TransactionId"); + + b.HasKey("Id"); + + b.ToTable("FlowNodes"); + }); + + modelBuilder.Entity("Model.DbModels.Fund", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Amount"); + + b.Property("Currency"); + + b.Property("ExchangeId"); + + b.HasKey("Id"); + + b.ToTable("Funds"); + }); + + modelBuilder.Entity("Model.DbModels.Setting", b => + { + b.Property("Key"); + + b.Property("Value"); + + b.HasKey("Key"); + + b.ToTable("Settings"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Model/Migrations/20180208101403_AddLinkComment.cs b/Model/Migrations/20180208101403_AddLinkComment.cs new file mode 100644 index 0000000..87df46d --- /dev/null +++ b/Model/Migrations/20180208101403_AddLinkComment.cs @@ -0,0 +1,24 @@ +using Microsoft.EntityFrameworkCore.Migrations; +using System; +using System.Collections.Generic; + +namespace Model.Migrations +{ + public partial class AddLinkComment : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "Comment", + table: "FlowLinks", + nullable: true); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "Comment", + table: "FlowLinks"); + } + } +} diff --git a/Model/Migrations/20180208142344_AddExchangeIdToFlowLink.Designer.cs b/Model/Migrations/20180208142344_AddExchangeIdToFlowLink.Designer.cs new file mode 100644 index 0000000..b90070d --- /dev/null +++ b/Model/Migrations/20180208142344_AddExchangeIdToFlowLink.Designer.cs @@ -0,0 +1,189 @@ +// +using CryptoManager.Models; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage; +using Microsoft.EntityFrameworkCore.Storage.Internal; +using Model.Enums; +using System; + +namespace Model.Migrations +{ + [DbContext(typeof(CryptoContext))] + [Migration("20180208142344_AddExchangeIdToFlowLink")] + partial class AddExchangeIdToFlowLink + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.0.1-rtm-125"); + + modelBuilder.Entity("Model.DbModels.CryptoTransaction", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BuyAmount"); + + b.Property("BuyCurrency"); + + b.Property("BuyFiatAmount"); + + b.Property("BuyFiatRate"); + + b.Property("Comment"); + + b.Property("DateTime"); + + b.Property("ExchangeId"); + + b.Property("FeeAmount"); + + b.Property("FeeCurrency"); + + b.Property("FromAddress"); + + b.Property("InAmount"); + + b.Property("InCurrency"); + + b.Property("OutAmount"); + + b.Property("OutCurrency"); + + b.Property("Rate"); + + b.Property("SellAmount"); + + b.Property("SellCurrency"); + + b.Property("ToAddress"); + + b.Property("TransactionHash"); + + b.Property("TransactionKey"); + + b.Property("Type"); + + b.HasKey("Id"); + + b.ToTable("Transactions"); + }); + + modelBuilder.Entity("Model.DbModels.Exchange", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Comment"); + + b.Property("ExchangeId"); + + b.Property("Passphrase"); + + b.Property("PrivateKey"); + + b.Property("PublicKey"); + + b.HasKey("Id"); + + b.ToTable("Exchanges"); + }); + + modelBuilder.Entity("Model.DbModels.FiatBalance", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Currency"); + + b.Property("ExchangeId"); + + b.Property("Invested"); + + b.Property("Payout"); + + b.HasKey("Id"); + + b.ToTable("FiatBalances"); + }); + + modelBuilder.Entity("Model.DbModels.FlowLink", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Amount"); + + b.Property("Comment"); + + b.Property("Currency"); + + b.Property("DateTime"); + + b.Property("ExchangeId"); + + b.Property("FlowNodeSource"); + + b.Property("FlowNodeTarget"); + + b.HasKey("Id"); + + b.ToTable("FlowLinks"); + }); + + modelBuilder.Entity("Model.DbModels.FlowNode", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Amount"); + + b.Property("Comment"); + + b.Property("Currency"); + + b.Property("DateTime"); + + b.Property("ExchangeId"); + + b.Property("TransactionId"); + + b.HasKey("Id"); + + b.ToTable("FlowNodes"); + }); + + modelBuilder.Entity("Model.DbModels.Fund", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Amount"); + + b.Property("Currency"); + + b.Property("ExchangeId"); + + b.HasKey("Id"); + + b.ToTable("Funds"); + }); + + modelBuilder.Entity("Model.DbModels.Setting", b => + { + b.Property("Key"); + + b.Property("Value"); + + b.HasKey("Key"); + + b.ToTable("Settings"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Model/Migrations/20180208142344_AddExchangeIdToFlowLink.cs b/Model/Migrations/20180208142344_AddExchangeIdToFlowLink.cs new file mode 100644 index 0000000..f564848 --- /dev/null +++ b/Model/Migrations/20180208142344_AddExchangeIdToFlowLink.cs @@ -0,0 +1,25 @@ +using Microsoft.EntityFrameworkCore.Migrations; +using System; +using System.Collections.Generic; + +namespace Model.Migrations +{ + public partial class AddExchangeIdToFlowLink : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "ExchangeId", + table: "FlowLinks", + nullable: false, + defaultValue: new Guid("00000000-0000-0000-0000-000000000000")); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "ExchangeId", + table: "FlowLinks"); + } + } +} diff --git a/Model/Migrations/CryptoContextModelSnapshot.cs b/Model/Migrations/CryptoContextModelSnapshot.cs index c3c69e6..b6cad82 100644 --- a/Model/Migrations/CryptoContextModelSnapshot.cs +++ b/Model/Migrations/CryptoContextModelSnapshot.cs @@ -110,6 +110,52 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("FiatBalances"); }); + modelBuilder.Entity("Model.DbModels.FlowLink", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Amount"); + + b.Property("Comment"); + + b.Property("Currency"); + + b.Property("DateTime"); + + b.Property("ExchangeId"); + + b.Property("FlowNodeSource"); + + b.Property("FlowNodeTarget"); + + b.HasKey("Id"); + + b.ToTable("FlowLinks"); + }); + + modelBuilder.Entity("Model.DbModels.FlowNode", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Amount"); + + b.Property("Comment"); + + b.Property("Currency"); + + b.Property("DateTime"); + + b.Property("ExchangeId"); + + b.Property("TransactionId"); + + b.HasKey("Id"); + + b.ToTable("FlowNodes"); + }); + modelBuilder.Entity("Model.DbModels.Fund", b => { b.Property("Id") diff --git a/Model/Model.csproj b/Model/Model.csproj index 46009fc..2115b48 100644 --- a/Model/Model.csproj +++ b/Model/Model.csproj @@ -9,8 +9,4 @@
    - - - - diff --git a/Plugins/IMarketData.cs b/Plugins/IMarketData.cs index 3727d96..9474f2a 100644 --- a/Plugins/IMarketData.cs +++ b/Plugins/IMarketData.cs @@ -11,5 +11,6 @@ public interface IMarketData //Task GetCurrentRate(string baseCurrency, IEnumerable currencies); Task GetHistoricRate(string baseCurrency, string currency, DateTime time); Task GetCoinInfo(string symbol); + bool IsFiat(string symbol); } } diff --git a/Plugins/MarketData/CryptoCompare.cs b/Plugins/MarketData/CryptoCompare.cs index ee48bdb..80a0aab 100644 --- a/Plugins/MarketData/CryptoCompare.cs +++ b/Plugins/MarketData/CryptoCompare.cs @@ -15,7 +15,9 @@ public class CryptoCompare : IMarketData private static readonly MemoryCache Cache = new MemoryCache(new MemoryCacheOptions()); private static readonly ILogger Logger = LogManager.GetCurrentClassLogger(); - private static Dictionary CoinCache = new Dictionary(); + private static readonly Dictionary CoinCache = new Dictionary(); + + private static readonly string[] FiatCurrencies = {"EUR", "CHF", "USD"}; #endregion @@ -29,13 +31,10 @@ public async Task GetCurrentRate(string baseCurrency, string currency) Exception ex = null; // Try 3 times, or return 0 - for (int i = 0; i < 3; i++) - { + for (var i = 0; i < 3; i++) try { - - - var x = await client.Prices.SingleAsync(currency, new[] { baseCurrency }, true); + var x = await client.Prices.SingleAsync(currency, new[] {baseCurrency}, true); var res = x.First().Value; return res; } @@ -45,7 +44,7 @@ public async Task GetCurrentRate(string baseCurrency, string currency) ex = e; await Task.Delay(5000); } - } + Logger.Error(ex, "Failted to get Historic Market data"); return 0; }); @@ -58,23 +57,20 @@ public async Task GetHistoricRate(string baseCurrency, string currency, Exception ex = null; // Try 3 times, or return a error - for (int i = 0; i < 3; i++) - { + for (var i = 0; i < 3; i++) try { - var x = await client.Prices.HistoricalAsync(currency, new[] { baseCurrency }, time); + var x = await client.Prices.HistoricalAsync(currency, new[] {baseCurrency}, time); return x.First().Value.First().Value; - } catch (Exception e) { Logger.Warn(e); ex = e; await Task.Delay(5000); - } - } + throw new Exception("Failted to get Historic Market data", ex); } @@ -85,15 +81,13 @@ public async Task GetCoinInfo(string symbol) var client = new CryptoCompareClient(); var data = await client.Coins.ListAsync(); foreach (var coin in data.Coins) - { - CoinCache.Add(coin.Value.Symbol, new CoinMeta() + CoinCache.Add(coin.Value.Symbol, new CoinMeta { Symbol = coin.Value.Symbol, ImageUrl = data.BaseImageUrl + coin.Value.ImageUrl, Name = coin.Value.CoinName, Url = data.BaseImageUrl + coin.Value.Url }); - } } if (CoinCache.ContainsKey(symbol)) @@ -102,6 +96,11 @@ public async Task GetCoinInfo(string symbol) return null; } + public bool IsFiat(string symbol) + { + return FiatCurrencies.Contains(symbol); + } + #endregion } } diff --git a/Plugins/Plugins.csproj b/Plugins/Plugins.csproj index 6296fa2..f630c1a 100644 --- a/Plugins/Plugins.csproj +++ b/Plugins/Plugins.csproj @@ -9,13 +9,13 @@ - - + + - +