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.
|
||||
// See LICENSE for copying information.
|
||||
|
||||
import { HttpClient } from '@/private/http/client';
|
||||
|
||||
/**
|
||||
* ErrorUnauthorized is a custom error type for performing unauthorized operations.
|
||||
*/
|
||||
@ -27,3 +29,37 @@ export class InternalError extends Error {
|
||||
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.
|
||||
// See LICENSE for copying information.
|
||||
|
||||
import { BadRequestError, InternalError, UnauthorizedError } from '@/api/index';
|
||||
import { APIClient } from '@/api/index';
|
||||
import { CreateNodeFields, Node, NodeURL } from '@/nodes';
|
||||
import { HttpClient } from '@/private/http/client';
|
||||
|
||||
/**
|
||||
* client for nodes controller of MND api.
|
||||
*/
|
||||
export class NodesClient {
|
||||
private readonly http: HttpClient = new HttpClient();
|
||||
export class NodesClient extends APIClient {
|
||||
private readonly ROOT_PATH: string = '/api/v0/nodes';
|
||||
|
||||
/**
|
||||
@ -176,31 +174,4 @@ export class NodesClient {
|
||||
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 { NodesClient } from '@/api/nodes';
|
||||
import { PayoutsClient } from '@/api/payouts';
|
||||
import { NodesModule, NodesState } from '@/app/store/nodes';
|
||||
import { PayoutsModule, PayoutsState } from '@/app/store/payouts';
|
||||
import { Nodes } from '@/nodes/service';
|
||||
import { Payouts } from '@/payouts/service';
|
||||
|
||||
Vue.use(Vuex);
|
||||
|
||||
@ -15,6 +18,7 @@ Vue.use(Vuex);
|
||||
*/
|
||||
export class RootState {
|
||||
nodes: NodesState;
|
||||
payouts: PayoutsState;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -25,13 +29,15 @@ export class MultinodeStoreOptions implements StoreOptions<RootState> {
|
||||
public readonly state: RootState;
|
||||
public readonly modules: ModuleTree<RootState>;
|
||||
|
||||
public constructor(nodes: NodesModule) {
|
||||
public constructor(nodes: NodesModule, payouts: PayoutsModule) {
|
||||
this.strict = true;
|
||||
this.state = {
|
||||
nodes: nodes.state,
|
||||
payouts: payouts.state,
|
||||
};
|
||||
this.modules = {
|
||||
nodes,
|
||||
payouts,
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -39,11 +45,14 @@ export class MultinodeStoreOptions implements StoreOptions<RootState> {
|
||||
// Services
|
||||
const nodesClient: NodesClient = new NodesClient();
|
||||
const nodesService: Nodes = new Nodes(nodesClient);
|
||||
const payoutsClient: PayoutsClient = new PayoutsClient();
|
||||
const payoutsService: Payouts = new Payouts(payoutsClient);
|
||||
|
||||
// Modules
|
||||
const nodesModule: NodesModule = new NodesModule(nodesService);
|
||||
const payoutsModule: PayoutsModule = new PayoutsModule(payoutsService);
|
||||
|
||||
// Store
|
||||
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