FF-216: Refactor reports (#306)

* started to refactor reports

* refactored

* fixed dialyzer
This commit is contained in:
Артем 2020-09-24 16:03:29 +03:00 committed by GitHub
parent 3412cb95ab
commit b07d9cd906
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 347 additions and 57 deletions

View File

@ -14,7 +14,6 @@
-export([marshal/2]).
-export([unmarshal/2]).
%% This special functions hasn't got opposite functions.
-spec unmarshal_identity_params(ff_proto_identity_thrift:'IdentityParams'()) ->
ff_identity_machine:params().

View File

@ -6,6 +6,9 @@
-type id() :: binary().
-type status() :: binary().
-type result(T, E) :: {ok, T} | {error, E}.
-type identity_state() :: ff_proto_identity_thrift:'IdentityState'().
-export_type([identity_state/0]).
-export([create_identity/2]).
-export([get_identity/2]).
@ -16,6 +19,8 @@
-export([get_identity_challenge_events/2]).
-export([get_identity_challenge_event/2]).
-export([get_thrift_identity/2]).
-include_lib("fistful_proto/include/ff_proto_identity_thrift.hrl").
-include_lib("fistful_proto/include/ff_proto_base_thrift.hrl").
@ -27,17 +32,11 @@
{error, {identity, unauthorized}} .
get_identity(IdentityID, HandlerContext) ->
Request = {fistful_identity, 'Get', [IdentityID, #'EventRange'{}]},
case service_call(Request, HandlerContext) of
case get_thrift_identity(IdentityID, HandlerContext) of
{ok, IdentityThrift} ->
case wapi_access_backend:check_resource(identity, IdentityThrift, HandlerContext) of
ok ->
{ok, unmarshal(identity, IdentityThrift)};
{error, unauthorized} ->
{error, {identity, unauthorized}}
end;
{exception, #fistful_IdentityNotFound{}} ->
{error, {identity, notfound}}
{error, _} = Error ->
Error
end.
-spec create_identity(params(), handler_context()) -> result(map(),
@ -235,6 +234,25 @@ get_identity_challenge_event_(#{
{error, Details}
end.
-spec get_thrift_identity(id(), handler_context()) ->
{ok, identity_state()} |
{error, {identity, notfound}} |
{error, {identity, unauthorized}} .
get_thrift_identity(IdentityID, HandlerContext) ->
Request = {fistful_identity, 'Get', [IdentityID, #'EventRange'{}]},
case service_call(Request, HandlerContext) of
{ok, IdentityThrift} ->
case wapi_access_backend:check_resource(identity, IdentityThrift, HandlerContext) of
ok ->
{ok, IdentityThrift};
{error, unauthorized} ->
{error, {identity, unauthorized}}
end;
{exception, #fistful_IdentityNotFound{}} ->
{error, {identity, notfound}}
end.
%%
%% Internal
%%

View File

@ -0,0 +1,197 @@
-module(wapi_report_backend).
-include_lib("fistful_reporter_proto/include/ff_reporter_reports_thrift.hrl").
-include_lib("file_storage_proto/include/fs_file_storage_thrift.hrl").
-include_lib("fistful_proto/include/ff_proto_base_thrift.hrl").
-include_lib("fistful_proto/include/ff_proto_identity_thrift.hrl").
-export([create_report/2]).
-export([get_report/3]).
-export([get_reports/2]).
-export([download_file/3]).
-type id() :: binary().
-type req_data() :: wapi_handler:req_data().
-type handler_context() :: wapi_handler:context().
-type response_data() :: wapi_handler:response_data().
-spec create_report(req_data(), handler_context()) ->
{ok, response_data()} | {error, Error}
when Error ::
{identity, unauthorized} |
{identity, notfound} |
invalid_request |
invalid_contract.
create_report(#{
identityID := IdentityID,
'ReportParams' := ReportParams
}, HandlerContext) ->
case get_contract_id_from_identity(IdentityID, HandlerContext) of
{ok, ContractID} ->
Req = create_report_request(#{
party_id => wapi_handler_utils:get_owner(HandlerContext),
contract_id => ContractID,
from_time => get_time(<<"fromTime">>, ReportParams),
to_time => get_time(<<"toTime">>, ReportParams)
}),
Call = {fistful_report, 'GenerateReport', [Req, maps:get(<<"reportType">>, ReportParams)]},
case wapi_handler_utils:service_call(Call, HandlerContext) of
{ok, ReportID} ->
get_report(contractID, ReportID, ContractID, HandlerContext);
{exception, #ff_reports_InvalidRequest{}} ->
{error, invalid_request};
{exception, #ff_reports_ContractNotFound{}} ->
{error, invalid_contract}
end;
{error, _} = Error ->
Error
end.
-spec get_report(integer(), binary(), handler_context()) ->
{ok, response_data()} | {error, Error}
when Error ::
{identity, unauthorized} |
{identity, notfound} |
notfound.
get_report(ReportID, IdentityID, HandlerContext) ->
get_report(identityID, ReportID, IdentityID, HandlerContext).
get_report(identityID, ReportID, IdentityID, HandlerContext) ->
case get_contract_id_from_identity(IdentityID, HandlerContext) of
{ok, ContractID} ->
get_report(contractID, ReportID, ContractID, HandlerContext);
{error, _} = Error ->
Error
end;
get_report(contractID, ReportID, ContractID, HandlerContext) ->
PartyID = wapi_handler_utils:get_owner(HandlerContext),
Call = {fistful_report, 'GetReport', [PartyID, ContractID, ReportID]},
case wapi_handler_utils:service_call(Call, HandlerContext) of
{ok, Report} ->
{ok, unmarshal_report(Report)};
{exception, #ff_reports_ReportNotFound{}} ->
{error, notfound}
end.
-spec get_reports(req_data(), handler_context()) ->
{ok, response_data()} | {error, Error}
when Error ::
{identity, unauthorized} |
{identity, notfound} |
invalid_request |
{dataset_too_big, integer()}.
get_reports(#{identityID := IdentityID} = Params, HandlerContext) ->
case get_contract_id_from_identity(IdentityID, HandlerContext) of
{ok, ContractID} ->
Req = create_report_request(#{
party_id => wapi_handler_utils:get_owner(HandlerContext),
contract_id => ContractID,
from_time => get_time(fromTime, Params),
to_time => get_time(toTime, Params)
}),
Call = {fistful_report, 'GetReports', [Req, [genlib:to_binary(maps:get(type, Params))]]},
case wapi_handler_utils:service_call(Call, HandlerContext) of
{ok, ReportList} ->
{ok, unmarshal_reports(ReportList)};
{exception, #ff_reports_InvalidRequest{}} ->
{error, invalid_request};
{exception, #ff_reports_DatasetTooBig{limit = Limit}} ->
{error, {dataset_too_big, Limit}}
end;
{error, _} = Error ->
Error
end.
-spec download_file(binary(), binary(), handler_context()) ->
{ok, response_data()} | {error, Error}
when Error ::
notfound.
download_file(FileID, ExpiresAt, HandlerContext) ->
Timestamp = wapi_utils:to_universal_time(ExpiresAt),
Call = {file_storage, 'GenerateDownloadUrl', [FileID, Timestamp]},
case wapi_handler_utils:service_call(Call, HandlerContext) of
{exception, #file_storage_FileNotFound{}} ->
{error, notfound};
Result->
Result
end.
%% Internal
-spec get_contract_id_from_identity(id(), handler_context()) ->
{ok, id()} | {error, Error}
when Error ::
{identity, unauthorized} |
{identity, notfound}.
get_contract_id_from_identity(IdentityID, HandlerContext) ->
case wapi_identity_backend:get_thrift_identity(IdentityID, HandlerContext) of
{ok, #idnt_IdentityState{contract_id = ContractID}} ->
{ok, ContractID};
{error, _} = Error ->
Error
end.
create_report_request(#{
party_id := PartyID,
contract_id := ContractID,
from_time := FromTime,
to_time := ToTime
}) ->
#'ff_reports_ReportRequest'{
party_id = PartyID,
contract_id = ContractID,
time_range = #'ff_reports_ReportTimeRange'{
from_time = FromTime,
to_time = ToTime
}
}.
get_time(Key, Req) ->
case genlib_map:get(Key, Req) of
Timestamp when is_binary(Timestamp) ->
wapi_utils:to_universal_time(Timestamp);
undefined ->
undefined
end.
%% Marshaling
unmarshal_reports(List) ->
lists:map(fun(Report) -> unmarshal_report(Report) end, List).
unmarshal_report(#ff_reports_Report{
report_id = ReportID,
time_range = TimeRange,
created_at = CreatedAt,
report_type = Type,
status = Status,
file_data_ids = Files
}) ->
genlib_map:compact(#{
<<"id">> => ReportID,
<<"fromTime">> => TimeRange#ff_reports_ReportTimeRange.from_time,
<<"toTime">> => TimeRange#ff_reports_ReportTimeRange.to_time,
<<"createdAt">> => CreatedAt,
<<"status">> => unmarshal_report_status(Status),
<<"type">> => Type,
<<"files">> => unmarshal_report_files(Files)
}).
unmarshal_report_status(pending) ->
<<"pending">>;
unmarshal_report_status(created) ->
<<"created">>;
unmarshal_report_status(canceled) ->
<<"canceled">>.
unmarshal_report_files(undefined) ->
[];
unmarshal_report_files(Files) ->
lists:map(fun(File) -> unmarshal_report_file(File) end, Files).
unmarshal_report_file(File) ->
#{<<"id">> => File}.

View File

@ -586,6 +586,83 @@ process_request('IssueP2PTransferTicket', #{
wapi_handler_utils:reply_error(404)
end;
%% Reports
process_request('CreateReport', Params, Context, _Opts) ->
case wapi_report_backend:create_report(Params, Context) of
{ok, Report} -> wapi_handler_utils:reply_ok(201, Report);
{error, {identity, notfound}} -> wapi_handler_utils:reply_ok(400, #{
<<"errorType">> => <<"NotFound">>,
<<"name">> => <<"identity">>,
<<"description">> => <<"identity not found">>
});
{error, {identity, unauthorized}} -> wapi_handler_utils:reply_ok(400, #{
<<"errorType">> => <<"NotFound">>,
<<"name">> => <<"identity">>,
<<"description">> => <<"identity not found">>
});
{error, invalid_request} -> wapi_handler_utils:reply_ok(400, #{
<<"errorType">> => <<"NoMatch">>,
<<"name">> => <<"timestamps">>,
<<"description">> => <<"invalid time range">>
});
{error, invalid_contract} -> wapi_handler_utils:reply_ok(400, #{
<<"errorType">> => <<"NotFound">>,
<<"name">> => <<"contractID">>,
<<"description">> => <<"contract not found">>
})
end;
process_request('GetReport', #{
identityID := IdentityID,
reportID := ReportId
}, Context, _Opts) ->
case wapi_report_backend:get_report(ReportId, IdentityID, Context) of
{ok, Report} -> wapi_handler_utils:reply_ok(200, Report);
{error, {identity, notfound}} -> wapi_handler_utils:reply_ok(400, #{
<<"errorType">> => <<"NotFound">>,
<<"name">> => <<"identity">>,
<<"description">> => <<"identity not found">>
});
{error, {identity, unauthorized}} -> wapi_handler_utils:reply_ok(400, #{
<<"errorType">> => <<"NotFound">>,
<<"name">> => <<"identity">>,
<<"description">> => <<"identity not found">>
});
{error, notfound} -> wapi_handler_utils:reply_ok(404)
end;
process_request('GetReports', Params, Context, _Opts) ->
case wapi_report_backend:get_reports(Params, Context) of
{ok, ReportList} -> wapi_handler_utils:reply_ok(200, ReportList);
{error, {identity, notfound}} -> wapi_handler_utils:reply_ok(400, #{
<<"errorType">> => <<"NotFound">>,
<<"name">> => <<"identity">>,
<<"description">> => <<"identity not found">>
});
{error, {identity, unauthorized}} -> wapi_handler_utils:reply_ok(400, #{
<<"errorType">> => <<"NotFound">>,
<<"name">> => <<"identity">>,
<<"description">> => <<"identity not found">>
});
{error, invalid_request} -> wapi_handler_utils:reply_ok(400, #{
<<"errorType">> => <<"NoMatch">>,
<<"name">> => <<"timestamps">>,
<<"description">> => <<"invalid time range">>
});
{error, {dataset_too_big, Limit}} -> wapi_handler_utils:reply_ok(400, #{
<<"errorType">> => <<"WrongLength">>,
<<"name">> => <<"limitExceeded">>,
<<"description">> => io_lib:format("Max limit: ~p", [Limit])
})
end;
process_request('DownloadFile', #{fileID := FileId}, Context, _Opts) ->
ExpiresAt = get_default_url_lifetime(),
case wapi_report_backend:download_file(FileId, ExpiresAt, Context) of
{ok, URL} ->
wapi_handler_utils:reply_ok(201, #{<<"url">> => URL, <<"expiresAt">> => ExpiresAt});
{error, notfound} ->
wapi_handler_utils:reply_ok(404)
end;
%% Fallback to legacy handler
process_request(OperationID, Params, Context, Opts) ->
@ -614,3 +691,10 @@ get_expiration_deadline(Expiration) ->
false ->
{error, expired}
end.
-define(DEFAULT_URL_LIFETIME, 60). % seconds
get_default_url_lifetime() ->
Now = erlang:system_time(second),
Lifetime = application:get_env(wapi, file_storage_url_lifetime, ?DEFAULT_URL_LIFETIME),
genlib_rfc3339:format(Now + Lifetime, second).

View File

@ -8,7 +8,6 @@
}).
-define(statusChange(Status), {status_changed, #wthd_StatusChange{status = Status}}).
-type req_data() :: wapi_handler:req_data().
-type handler_context() :: wapi_handler:context().
-type response_data() :: wapi_handler:response_data().

View File

@ -8,6 +8,7 @@
-include_lib("fistful_proto/include/ff_proto_base_thrift.hrl").
-include_lib("jose/include/jose_jwk.hrl").
-include_lib("wapi_wallet_dummy_data.hrl").
-include_lib("fistful_proto/include/ff_proto_identity_thrift.hrl").
-export([all/0]).
-export([groups/0]).
@ -130,17 +131,19 @@ end_per_testcase(_Name, C) ->
-spec create_report_ok_test(config()) ->
_.
create_report_ok_test(C) ->
{ok, Identity} = create_identity(C),
IdentityID = maps:get(<<"id">>, Identity),
wapi_ct_helper:mock_services([{fistful_report, fun
PartyID = ?config(party, C),
wapi_ct_helper:mock_services([
{fistful_report, fun
('GenerateReport', _) -> {ok, ?REPORT_ID};
('GetReport', _) -> {ok, ?REPORT}
end}], C),
end},
{fistful_identity, fun('Get', _) -> {ok, ?IDENTITY(PartyID)} end}
], C),
{ok, _} = call_api(
fun swag_client_wallet_reports_api:create_report/3,
#{
binding => #{
<<"identityID">> => IdentityID
<<"identityID">> => ?STRING
},
body => #{
<<"reportType">> => <<"withdrawalRegistry">>,
@ -154,16 +157,18 @@ create_report_ok_test(C) ->
-spec get_report_ok_test(config()) ->
_.
get_report_ok_test(C) ->
{ok, Identity} = create_identity(C),
IdentityID = maps:get(<<"id">>, Identity),
wapi_ct_helper:mock_services([{fistful_report, fun
PartyID = ?config(party, C),
wapi_ct_helper:mock_services([
{fistful_report, fun
('GetReport', _) -> {ok, ?REPORT}
end}], C),
end},
{fistful_identity, fun('Get', _) -> {ok, ?IDENTITY(PartyID)} end}
], C),
{ok, _} = call_api(
fun swag_client_wallet_reports_api:get_report/3,
#{
binding => #{
<<"identityID">> => IdentityID,
<<"identityID">> => ?STRING,
<<"reportID">> => ?INTEGER
}
},
@ -173,19 +178,21 @@ get_report_ok_test(C) ->
-spec get_reports_ok_test(config()) ->
_.
get_reports_ok_test(C) ->
{ok, Identity} = create_identity(C),
IdentityID = maps:get(<<"id">>, Identity),
wapi_ct_helper:mock_services([{fistful_report, fun
PartyID = ?config(party, C),
wapi_ct_helper:mock_services([
{fistful_report, fun
('GetReports', _) -> {ok, [
?REPORT_EXT(pending, []),
?REPORT_EXT(created, undefined),
?REPORT_WITH_STATUS(canceled)]}
end}], C),
end},
{fistful_identity, fun('Get', _) -> {ok, ?IDENTITY(PartyID)} end}
], C),
{ok, _} = call_api(
fun swag_client_wallet_reports_api:get_reports/3,
#{
binding => #{
<<"identityID">> => IdentityID
<<"identityID">> => ?STRING
},
qs_val => #{
<<"fromTime">> => ?TIMESTAMP,
@ -200,11 +207,14 @@ get_reports_ok_test(C) ->
_.
reports_with_wrong_identity_ok_test(C) ->
IdentityID = <<"WrongIdentity">>,
wapi_ct_helper:mock_services([{fistful_report, fun
wapi_ct_helper:mock_services([
{fistful_report, fun
('GenerateReport', _) -> {ok, ?REPORT_ID};
('GetReport', _) -> {ok, ?REPORT};
('GetReports', _) -> {ok, [?REPORT, ?REPORT, ?REPORT]}
end}], C),
end},
{fistful_identity, fun('Get', _) -> throw(#fistful_IdentityNotFound{}) end}
], C),
?emptyresp(400) = call_api(
fun swag_client_wallet_reports_api:create_report/3,
#{
@ -276,21 +286,3 @@ create_party(_C) ->
ID = genlib:bsuuid(),
_ = ff_party:create(ID),
ID.
create_identity(C) ->
PartyID = ?config(party, C),
Params = #{
<<"provider">> => <<"good-one">>,
<<"class">> => <<"person">>,
<<"name">> => <<"HAHA NO2">>
},
wapi_wallet_ff_backend:create_identity(Params, create_context(PartyID, C)).
create_context(PartyID, C) ->
maps:merge(wapi_ct_helper:create_auth_ctx(PartyID), create_woody_ctx(C)).
create_woody_ctx(C) ->
#{
woody_context => ct_helper:get_woody_ctx(C)
}.

View File

@ -177,6 +177,7 @@
name = ?STRING,
party_id = ?STRING,
provider_id = ?STRING,
contract_id = ?STRING,
class_id = ?STRING,
metadata = ?DEFAULT_METADATA(),
context = Context