web/multinode: payout api
WHAT: multinode payout types, api and store WHY: preparation of logic for mnd payouts page implementation Change-Id: I4f2ea78056eab84c482853ef7a6c4cab4fb4c04f
This commit is contained in:
parent
c08ca361d8
commit
ce075a1d53
@ -1,6 +1,8 @@
|
|||||||
// Copyright (C) 2021 Storj Labs, Inc.
|
// Copyright (C) 2021 Storj Labs, Inc.
|
||||||
// See LICENSE for copying information.
|
// See LICENSE for copying information.
|
||||||
|
|
||||||
|
import { HttpClient } from '@/private/http/client';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ErrorUnauthorized is a custom error type for performing unauthorized operations.
|
* ErrorUnauthorized is a custom error type for performing unauthorized operations.
|
||||||
*/
|
*/
|
||||||
@ -27,3 +29,37 @@ export class InternalError extends Error {
|
|||||||
super(message);
|
super(message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* APIClient is base client that holds http client and error handler.
|
||||||
|
*/
|
||||||
|
export class APIClient {
|
||||||
|
protected readonly http: HttpClient = new HttpClient();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* handles error due to response code.
|
||||||
|
* @param response - response from server.
|
||||||
|
*
|
||||||
|
* @throws {@link BadRequestError}
|
||||||
|
* This exception is thrown if the input is not a valid ISBN number.
|
||||||
|
*
|
||||||
|
* @throws {@link UnauthorizedError}
|
||||||
|
* Thrown if the ISBN number is valid, but no such book exists in the catalog.
|
||||||
|
*
|
||||||
|
* @throws {@link InternalError}
|
||||||
|
* Thrown if the ISBN number is valid, but no such book exists in the catalog.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
protected async handleError(response: Response): Promise<void> {
|
||||||
|
const body = await response.json();
|
||||||
|
|
||||||
|
switch (response.status) {
|
||||||
|
case 401: throw new UnauthorizedError(body.error);
|
||||||
|
case 400: throw new BadRequestError(body.error);
|
||||||
|
case 500:
|
||||||
|
default:
|
||||||
|
throw new InternalError(body.error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,15 +1,13 @@
|
|||||||
// Copyright (C) 2021 Storj Labs, Inc.
|
// Copyright (C) 2021 Storj Labs, Inc.
|
||||||
// See LICENSE for copying information.
|
// See LICENSE for copying information.
|
||||||
|
|
||||||
import { BadRequestError, InternalError, UnauthorizedError } from '@/api/index';
|
import { APIClient } from '@/api/index';
|
||||||
import { CreateNodeFields, Node, NodeURL } from '@/nodes';
|
import { CreateNodeFields, Node, NodeURL } from '@/nodes';
|
||||||
import { HttpClient } from '@/private/http/client';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* client for nodes controller of MND api.
|
* client for nodes controller of MND api.
|
||||||
*/
|
*/
|
||||||
export class NodesClient {
|
export class NodesClient extends APIClient {
|
||||||
private readonly http: HttpClient = new HttpClient();
|
|
||||||
private readonly ROOT_PATH: string = '/api/v0/nodes';
|
private readonly ROOT_PATH: string = '/api/v0/nodes';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -176,31 +174,4 @@ export class NodesClient {
|
|||||||
url.Name,
|
url.Name,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* handles error due to response code.
|
|
||||||
* @param response - response from server.
|
|
||||||
*
|
|
||||||
* @throws {@link BadRequestError}
|
|
||||||
* This exception is thrown if the input is not a valid ISBN number.
|
|
||||||
*
|
|
||||||
* @throws {@link UnauthorizedError}
|
|
||||||
* Thrown if the ISBN number is valid, but no such book exists in the catalog.
|
|
||||||
*
|
|
||||||
* @throws {@link InternalError}
|
|
||||||
* Thrown if the ISBN number is valid, but no such book exists in the catalog.
|
|
||||||
*
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
private async handleError(response: Response): Promise<void> {
|
|
||||||
const body = await response.json();
|
|
||||||
|
|
||||||
switch (response.status) {
|
|
||||||
case 401: throw new UnauthorizedError(body.error);
|
|
||||||
case 400: throw new BadRequestError(body.error);
|
|
||||||
case 500:
|
|
||||||
default:
|
|
||||||
throw new InternalError(body.error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
38
web/multinode/src/api/payouts.ts
Normal file
38
web/multinode/src/api/payouts.ts
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
// Copyright (C) 2021 Storj Labs, Inc.
|
||||||
|
// See LICENSE for copying information.
|
||||||
|
|
||||||
|
import { APIClient } from '@/api/index';
|
||||||
|
import { PayoutsSummary } from '@/payouts';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* client for nodes controller of MND api.
|
||||||
|
*/
|
||||||
|
export class PayoutsClient extends APIClient {
|
||||||
|
private readonly ROOT_PATH: string = '/api/v0/payouts';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* handles fetch of payouts summary information.
|
||||||
|
*
|
||||||
|
* @param satelliteId - satellite id.
|
||||||
|
* @param period - selected period.
|
||||||
|
*
|
||||||
|
* @throws {@link BadRequestError}
|
||||||
|
* This exception is thrown if the input is not a valid.
|
||||||
|
*
|
||||||
|
* @throws {@link UnauthorizedError}
|
||||||
|
* Thrown if the auth cookie is missing or invalid.
|
||||||
|
*
|
||||||
|
* @throws {@link InternalError}
|
||||||
|
* Thrown if something goes wrong on server side.
|
||||||
|
*/
|
||||||
|
public async summary(satelliteId: string | null, period: string | null): Promise<PayoutsSummary> {
|
||||||
|
const path = `${this.ROOT_PATH}/summary`;
|
||||||
|
const response = await this.http.get(path);
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
await this.handleError(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
return await response.json();
|
||||||
|
}
|
||||||
|
}
|
@ -5,8 +5,11 @@ import Vue from 'vue';
|
|||||||
import Vuex, { ModuleTree, Store, StoreOptions } from 'vuex';
|
import Vuex, { ModuleTree, Store, StoreOptions } from 'vuex';
|
||||||
|
|
||||||
import { NodesClient } from '@/api/nodes';
|
import { NodesClient } from '@/api/nodes';
|
||||||
|
import { PayoutsClient } from '@/api/payouts';
|
||||||
import { NodesModule, NodesState } from '@/app/store/nodes';
|
import { NodesModule, NodesState } from '@/app/store/nodes';
|
||||||
|
import { PayoutsModule, PayoutsState } from '@/app/store/payouts';
|
||||||
import { Nodes } from '@/nodes/service';
|
import { Nodes } from '@/nodes/service';
|
||||||
|
import { Payouts } from '@/payouts/service';
|
||||||
|
|
||||||
Vue.use(Vuex);
|
Vue.use(Vuex);
|
||||||
|
|
||||||
@ -15,6 +18,7 @@ Vue.use(Vuex);
|
|||||||
*/
|
*/
|
||||||
export class RootState {
|
export class RootState {
|
||||||
nodes: NodesState;
|
nodes: NodesState;
|
||||||
|
payouts: PayoutsState;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -25,13 +29,15 @@ export class MultinodeStoreOptions implements StoreOptions<RootState> {
|
|||||||
public readonly state: RootState;
|
public readonly state: RootState;
|
||||||
public readonly modules: ModuleTree<RootState>;
|
public readonly modules: ModuleTree<RootState>;
|
||||||
|
|
||||||
public constructor(nodes: NodesModule) {
|
public constructor(nodes: NodesModule, payouts: PayoutsModule) {
|
||||||
this.strict = true;
|
this.strict = true;
|
||||||
this.state = {
|
this.state = {
|
||||||
nodes: nodes.state,
|
nodes: nodes.state,
|
||||||
|
payouts: payouts.state,
|
||||||
};
|
};
|
||||||
this.modules = {
|
this.modules = {
|
||||||
nodes,
|
nodes,
|
||||||
|
payouts,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -39,11 +45,14 @@ export class MultinodeStoreOptions implements StoreOptions<RootState> {
|
|||||||
// Services
|
// Services
|
||||||
const nodesClient: NodesClient = new NodesClient();
|
const nodesClient: NodesClient = new NodesClient();
|
||||||
const nodesService: Nodes = new Nodes(nodesClient);
|
const nodesService: Nodes = new Nodes(nodesClient);
|
||||||
|
const payoutsClient: PayoutsClient = new PayoutsClient();
|
||||||
|
const payoutsService: Payouts = new Payouts(payoutsClient);
|
||||||
|
|
||||||
// Modules
|
// Modules
|
||||||
const nodesModule: NodesModule = new NodesModule(nodesService);
|
const nodesModule: NodesModule = new NodesModule(nodesService);
|
||||||
|
const payoutsModule: PayoutsModule = new PayoutsModule(payoutsService);
|
||||||
|
|
||||||
// Store
|
// Store
|
||||||
export const store: Store<RootState> = new Vuex.Store<RootState>(
|
export const store: Store<RootState> = new Vuex.Store<RootState>(
|
||||||
new MultinodeStoreOptions(nodesModule),
|
new MultinodeStoreOptions(nodesModule, payoutsModule),
|
||||||
);
|
);
|
||||||
|
60
web/multinode/src/app/store/payouts.ts
Normal file
60
web/multinode/src/app/store/payouts.ts
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
// Copyright (C) 2021 Storj Labs, Inc.
|
||||||
|
// See LICENSE for copying information.
|
||||||
|
|
||||||
|
import { ActionContext, ActionTree, GetterTree, Module, MutationTree } from 'vuex';
|
||||||
|
|
||||||
|
import { RootState } from '@/app/store/index';
|
||||||
|
import { PayoutsSummary } from '@/payouts';
|
||||||
|
import { Payouts } from '@/payouts/service';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PayoutsState is a representation of payouts module state.
|
||||||
|
*/
|
||||||
|
export class PayoutsState {
|
||||||
|
public summary: PayoutsSummary;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* NodesModule is a part of a global store that encapsulates all nodes related logic.
|
||||||
|
*/
|
||||||
|
export class PayoutsModule implements Module<PayoutsState, RootState> {
|
||||||
|
public readonly namespaced: boolean;
|
||||||
|
public readonly state: PayoutsState;
|
||||||
|
public readonly getters?: GetterTree<PayoutsState, RootState>;
|
||||||
|
public readonly actions: ActionTree<PayoutsState, RootState>;
|
||||||
|
public readonly mutations: MutationTree<PayoutsState>;
|
||||||
|
|
||||||
|
private readonly payouts: any;
|
||||||
|
|
||||||
|
public constructor(payouts: Payouts) {
|
||||||
|
this.payouts = payouts;
|
||||||
|
|
||||||
|
this.namespaced = true;
|
||||||
|
this.state = new PayoutsState();
|
||||||
|
this.mutations = {
|
||||||
|
populate: this.populate,
|
||||||
|
};
|
||||||
|
this.actions = {
|
||||||
|
getSummary: this.getSummary.bind(this),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* populate mutation will set payouts state.
|
||||||
|
* @param state - state of the module.
|
||||||
|
* @param summary - payouts summary information depends on selected time and satellite.
|
||||||
|
*/
|
||||||
|
public populate(state: PayoutsState, summary: PayoutsSummary): void {
|
||||||
|
state.summary = summary;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* getSummary action loads payouts summary information.
|
||||||
|
* @param ctx - context of the Vuex action.
|
||||||
|
*/
|
||||||
|
public async getSummary(ctx: ActionContext<PayoutsState, RootState>): Promise<void> {
|
||||||
|
// @ts-ignore
|
||||||
|
const summary = await this.payouts.summary(ctx.rootState.nodes.selectedSatellite.id, '');
|
||||||
|
ctx.commit('populate', summary);
|
||||||
|
}
|
||||||
|
}
|
50
web/multinode/src/payouts/index.ts
Normal file
50
web/multinode/src/payouts/index.ts
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
// Copyright (C) 2021 Storj Labs, Inc.
|
||||||
|
// See LICENSE for copying information.
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PayoutsSummary is a representation of summary of payout information for node.
|
||||||
|
*/
|
||||||
|
export class NodePayoutsSummary {
|
||||||
|
public constructor(
|
||||||
|
public nodeID: string = '',
|
||||||
|
public nodeName: string = '',
|
||||||
|
public held: number = 0,
|
||||||
|
public paid: number = 0,
|
||||||
|
) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PayoutsSummary is a representation of summary of payout information for all selected nodes and list of payout summaries by nodes.
|
||||||
|
*/
|
||||||
|
export class PayoutsSummary {
|
||||||
|
public constructor(
|
||||||
|
public totalEarned: number = 0,
|
||||||
|
public totalHeld: number = 0,
|
||||||
|
public totalPaid: number = 0,
|
||||||
|
public nodeSummary: NodePayoutsSummary[] = [],
|
||||||
|
) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents payout period month and year.
|
||||||
|
*/
|
||||||
|
export class PayoutPeriod {
|
||||||
|
public constructor(
|
||||||
|
public year: number = new Date().getUTCFullYear(),
|
||||||
|
public month: number = new Date().getUTCMonth(),
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public get period(): string {
|
||||||
|
return this.month < 9 ? `${this.year}-0${this.month + 1}` : `${this.year}-${this.month + 1}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses PayoutPeriod from string.
|
||||||
|
* @param period string
|
||||||
|
*/
|
||||||
|
public static fromString(period: string): PayoutPeriod {
|
||||||
|
const periodArray = period.split('-');
|
||||||
|
|
||||||
|
return new PayoutPeriod(parseInt(periodArray[0]), parseInt(periodArray[1]) - 1);
|
||||||
|
}
|
||||||
|
}
|
47
web/multinode/src/payouts/service.ts
Normal file
47
web/multinode/src/payouts/service.ts
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
// Copyright (C) 2021 Storj Labs, Inc.
|
||||||
|
// See LICENSE for copying information.
|
||||||
|
|
||||||
|
import { PayoutsClient } from '@/api/payouts';
|
||||||
|
import { NodePayoutsSummary, PayoutsSummary } from '@/payouts/index';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* exposes all payouts related logic
|
||||||
|
*/
|
||||||
|
export class Payouts {
|
||||||
|
private readonly payouts: PayoutsClient;
|
||||||
|
|
||||||
|
public constructor(payouts: PayoutsClient) {
|
||||||
|
this.payouts = payouts;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* fetches of payouts summary information.
|
||||||
|
*
|
||||||
|
* @param satelliteId - satellite id.
|
||||||
|
* @param period - selected period.
|
||||||
|
*
|
||||||
|
* @throws {@link BadRequestError}
|
||||||
|
* This exception is thrown if the input is not a valid.
|
||||||
|
*
|
||||||
|
* @throws {@link UnauthorizedError}
|
||||||
|
* Thrown if the auth cookie is missing or invalid.
|
||||||
|
*
|
||||||
|
* @throws {@link InternalError}
|
||||||
|
* Thrown if something goes wrong on server side.
|
||||||
|
*/
|
||||||
|
public async summary(satelliteId: string | null, period: string | null): Promise<PayoutsSummary> {
|
||||||
|
const result = await this.payouts.summary(satelliteId, period);
|
||||||
|
|
||||||
|
return new PayoutsSummary(
|
||||||
|
result.totalEarned,
|
||||||
|
result.totalHeld,
|
||||||
|
result.totalPaid,
|
||||||
|
result.nodeSummary.map(item => new NodePayoutsSummary(
|
||||||
|
item.nodeID,
|
||||||
|
item.nodeName,
|
||||||
|
item.held,
|
||||||
|
item.paid,
|
||||||
|
)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user