mirror of
https://github.com/valitydev/fistful-server.git
synced 2024-11-06 02:35:18 +00:00
FF-216: Refactor reports (#306)
* started to refactor reports * refactored * fixed dialyzer
This commit is contained in:
parent
3412cb95ab
commit
b07d9cd906
@ -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().
|
||||
|
@ -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
|
||||
%%
|
||||
|
197
apps/wapi/src/wapi_report_backend.erl
Normal file
197
apps/wapi/src/wapi_report_backend.erl
Normal 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}.
|
@ -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).
|
||||
|
@ -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().
|
||||
|
@ -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)
|
||||
}.
|
||||
|
||||
|
@ -177,6 +177,7 @@
|
||||
name = ?STRING,
|
||||
party_id = ?STRING,
|
||||
provider_id = ?STRING,
|
||||
contract_id = ?STRING,
|
||||
class_id = ?STRING,
|
||||
metadata = ?DEFAULT_METADATA(),
|
||||
context = Context
|
||||
|
Loading…
Reference in New Issue
Block a user