From 278e12dcbe268c7808f7854b8265b933dc915251 Mon Sep 17 00:00:00 2001 From: Sean Yesmunt Date: Fri, 8 May 2020 16:47:33 -0400 Subject: [PATCH] add purchase_list --- dist/bundle.es.js | 171 +++++++++++++++++++++++++++++-- dist/flow-typed/Claim.js | 18 +++- dist/flow-typed/Lbry.js | 17 +++ flow-typed/Claim.js | 18 +++- flow-typed/Lbry.js | 17 +++ src/constants/action_types.js | 3 + src/index.js | 7 ++ src/lbry.js | 1 + src/redux/actions/claims.js | 42 +++++++- src/redux/reducers/claims.js | 73 ++++++++++++- src/redux/selectors/claims.js | 58 ++++++++++- src/redux/selectors/file_info.js | 13 +-- src/util/claim.js | 17 +++ 13 files changed, 421 insertions(+), 34 deletions(-) diff --git a/dist/bundle.es.js b/dist/bundle.es.js index 9fe02e5..066e944 100644 --- a/dist/bundle.es.js +++ b/dist/bundle.es.js @@ -149,6 +149,9 @@ const ADD_FILES_REFLECTING = 'ADD_FILES_REFLECTING'; const UPDATE_FILES_REFLECTING = 'UPDATE_FILES_REFLECTING'; const TOGGLE_CHECKING_REFLECTING = 'TOGGLE_CHECKING_REFLECTING'; const TOGGLE_CHECKING_PENDING = 'TOGGLE_CHECKING_PENDING'; +const PURCHASE_LIST_STARTED = 'PURCHASE_LIST_STARTED'; +const PURCHASE_LIST_COMPLETED = 'PURCHASE_LIST_COMPLETED'; +const PURCHASE_LIST_FAILED = 'PURCHASE_LIST_FAILED'; // Comments const COMMENT_LIST_STARTED = 'COMMENT_LIST_STARTED'; @@ -429,6 +432,9 @@ var action_types = /*#__PURE__*/Object.freeze({ UPDATE_FILES_REFLECTING: UPDATE_FILES_REFLECTING, TOGGLE_CHECKING_REFLECTING: TOGGLE_CHECKING_REFLECTING, TOGGLE_CHECKING_PENDING: TOGGLE_CHECKING_PENDING, + PURCHASE_LIST_STARTED: PURCHASE_LIST_STARTED, + PURCHASE_LIST_COMPLETED: PURCHASE_LIST_COMPLETED, + PURCHASE_LIST_FAILED: PURCHASE_LIST_FAILED, COMMENT_LIST_STARTED: COMMENT_LIST_STARTED, COMMENT_LIST_COMPLETED: COMMENT_LIST_COMPLETED, COMMENT_LIST_FAILED: COMMENT_LIST_FAILED, @@ -1090,6 +1096,7 @@ const Lbry = { transaction_list: (params = {}) => daemonCallWithResult('transaction_list', params), utxo_release: (params = {}) => daemonCallWithResult('utxo_release', params), support_abandon: (params = {}) => daemonCallWithResult('support_abandon', params), + purchase_list: (params = {}) => daemonCallWithResult('purchase_list', params), sync_hash: (params = {}) => daemonCallWithResult('sync_hash', params), sync_apply: (params = {}) => daemonCallWithResult('sync_apply', params), @@ -2136,7 +2143,21 @@ function createNormalizedClaimSearchKey(options) { return query; } +function filterClaims(claims, query) { + if (query) { + const queryMatchRegExp = new RegExp(query, 'i'); + return claims.filter(claim => { + const { value } = claim; + + return value.title && value.title.match(queryMatchRegExp) || claim.signing_channel && claim.signing_channel.name.match(queryMatchRegExp) || claim.name && claim.name.match(queryMatchRegExp); + }); + } + + return claims; +} + var _extends$4 = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; + const selectState$2 = state => state.claims || {}; const selectClaimsById = reselect.createSelector(selectState$2, state => state.byId || {}); @@ -2277,6 +2298,30 @@ const makeSelectClaimIsMine = rawUri => { }); }; +const selectMyPurchases = reselect.createSelector(selectState$2, state => state.myPurchases); + +const selectMyPurchasesCount = reselect.createSelector(selectState$2, state => state.myPurchasesPageTotalResults); + +const selectIsFetchingMyPurchases = reselect.createSelector(selectState$2, state => state.fetchingMyPurchases); + +const selectFetchingMyPurchasesError = reselect.createSelector(selectState$2, state => state.fetchingMyPurchasesError); + +const makeSelectMyPurchasesForPage = (query, page = 1) => reselect.createSelector(selectMyPurchases, selectClaimsByUri, (myPurchases, claimsByUri) => { + if (!myPurchases) { + return undefined; + } + + const fileInfos = myPurchases.map(uri => claimsByUri[uri]); + const matchingFileInfos = filterClaims(fileInfos, query); + const start = (Number(page) - 1) * Number(PAGE_SIZE); + const end = Number(page) * Number(PAGE_SIZE); + return matchingFileInfos && matchingFileInfos.length ? matchingFileInfos.slice(start, end).map(fileInfo => fileInfo.canonical_url || fileInfo.permanent_url) : []; +}); + +const makeSelectClaimWasPurchased = uri => reselect.createSelector(makeSelectClaimForUri(uri), claim => { + return claim && claim.purchase_receipt !== undefined; +}); + const selectAllFetchingChannelClaims = reselect.createSelector(selectState$2, state => state.fetchingChannelClaims || {}); const makeSelectFetchingChannelClaims = uri => reselect.createSelector(selectAllFetchingChannelClaims, fetching => fetching && fetching[uri]); @@ -2540,7 +2585,7 @@ const makeSelectCanonicalUrlForUri = uri => reselect.createSelector(makeSelectCl const makeSelectPermanentUrlForUri = uri => reselect.createSelector(makeSelectClaimForUri(uri), claim => claim && claim.permanent_url); const makeSelectSupportsForUri = uri => reselect.createSelector(selectSupportsByOutpoint, makeSelectClaimForUri(uri), (byOutpoint, claim) => { - if (!claim || !claim.is_mine) { + if (!claim || !claim.is_my_output) { return null; } @@ -3180,7 +3225,9 @@ function doResolveUris(uris, returnCachedClaims = false) { return; } - const options = {}; + const options = { + include_purchase_receipt: true + }; if (urisToResolve.length === 1) { options.include_is_my_output = true; @@ -3427,7 +3474,8 @@ function doFetchClaimsByChannel(uri, page = 1) { valid_channel_signature: true, page: page || 1, order_by: ['release_time'], - include_is_my_output: true + include_is_my_output: true, + include_purchase_receipt: true }).then(result => { const { items: claims, total_items: claimsInChannel, page: returnedPage } = result; @@ -3630,7 +3678,9 @@ function doClaimSearch(options = { }); }; - lbryProxy.claim_search(options).then(success, failure); + lbryProxy.claim_search(_extends$5({}, options, { + include_purchase_receipt: true + })).then(success, failure); }; } @@ -3700,6 +3750,38 @@ function doClearRepostError() { }; } +function doPurchaseList(page = 1, pageSize = 99999) { + return dispatch => { + dispatch({ + type: PURCHASE_LIST_STARTED + }); + + const success = result => { + return dispatch({ + type: PURCHASE_LIST_COMPLETED, + data: { + result + } + }); + }; + + const failure = error => { + dispatch({ + type: PURCHASE_LIST_FAILED, + data: { + error: error.message + } + }); + }; + + lbryProxy.purchase_list({ + page: page, + page_size: pageSize, + resolve: true + }).then(success, failure); + }; +} + const selectState$3 = state => state.fileInfo || {}; const selectFileInfosByOutpoint = reselect.createSelector(selectState$3, state => state.byOutpoint || {}); @@ -3820,6 +3902,7 @@ function filterFileInfos(fileInfos, query) { const queryMatchRegExp = new RegExp(query, 'i'); return fileInfos.filter(fileInfo => { const { metadata } = fileInfo; + return metadata.title && metadata.title.match(queryMatchRegExp) || fileInfo.channel_name && fileInfo.channel_name.match(queryMatchRegExp) || fileInfo.claim_name && fileInfo.claim_name.match(queryMatchRegExp); }); } @@ -5021,6 +5104,8 @@ const doToggleBlockChannel = uri => ({ var _extends$9 = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; +function _objectWithoutProperties$3(obj, keys) { var target = {}; for (var i in obj) { if (keys.indexOf(i) >= 0) continue; if (!Object.prototype.hasOwnProperty.call(obj, i)) continue; target[i] = obj[i]; } return target; } + const reducers = {}; const defaultState = { byId: {}, @@ -5029,10 +5114,13 @@ const defaultState = { channelClaimCounts: {}, fetchingChannelClaims: {}, resolvingUris: [], - // This should not be a Set - // Storing sets in reducers can cause issues myChannelClaims: undefined, myClaims: undefined, + myPurchases: undefined, + myPurchasesPageNumber: undefined, + myPurchasesPageTotalResults: undefined, + fetchingMyPurchases: false, + fetchingMyPurchasesError: undefined, fetchingMyChannels: false, abandoningById: {}, pendingById: {}, @@ -5053,6 +5141,7 @@ const defaultState = { myClaimsPageNumber: undefined, myClaimsPageTotalResults: undefined, isFetchingClaimListMine: false, + isFetchingMyPurchases: false, isCheckingNameForPublish: false, checkingPending: false, checkingReflecting: false @@ -5150,13 +5239,13 @@ reducers[FETCH_CLAIM_LIST_MINE_COMPLETED] = (state, action) => { const byUri = Object.assign({}, state.claimsByUri); const pendingById = Object.assign({}, state.pendingById); let myClaimIds = new Set(state.myClaims); - let urlPage = []; + let urlsForCurrentPage = []; claims.forEach(claim => { const uri = buildURI({ streamName: claim.name, streamClaimId: claim.claim_id }); const { claim_id: claimId } = claim; if (claim.type && claim.type.match(/claim|update/)) { - urlPage.push(uri); + urlsForCurrentPage.push(uri); if (claim.confirmations < 1) { pendingById[claimId] = claim; delete byId[claimId]; @@ -5188,7 +5277,7 @@ reducers[FETCH_CLAIM_LIST_MINE_COMPLETED] = (state, action) => { byId, claimsByUri: byUri, pendingById, - myClaimsPageResults: urlPage, + myClaimsPageResults: urlsForCurrentPage, myClaimsPageNumber: page, myClaimsPageTotalResults: totalItems }); @@ -5325,6 +5414,7 @@ reducers[UPDATE_PENDING_CLAIMS] = (state, action) => { const pendingById = Object.assign({}, state.pendingById); let myClaimIds = new Set(state.myClaims); + // $FlowFixMe claims.forEach(claim => { const uri = buildURI({ streamName: claim.name, streamClaimId: claim.claim_id }); const { claim_id: claimId } = claim; @@ -5570,6 +5660,58 @@ reducers[TOGGLE_CHECKING_PENDING] = (state, action) => { })); }; +reducers[PURCHASE_LIST_STARTED] = state => { + return _extends$9({}, state, { + fetchingMyPurchases: true, + fetchingMyPurchasesError: null + }); +}; + +reducers[PURCHASE_LIST_COMPLETED] = (state, action) => { + const { result } = action.data; + const page = result.page; + const totalItems = result.total_items; + + let byId = Object.assign({}, state.byId); + let byUri = Object.assign({}, state.claimsByUri); + let urlsForCurrentPage = []; + + result.items.forEach(item => { + if (!item.claim) { + // Abandoned claim + return; + } + + const { claim } = item, + purchaseInfo = _objectWithoutProperties$3(item, ['claim']); + claim.purchase_receipt = purchaseInfo; + const claimId = claim.claim_id; + const uri = claim.canonical_url; + + byId[claimId] = claim; + byUri[uri] = claimId; + urlsForCurrentPage.push(uri); + }); + + return Object.assign({}, state, { + byId, + claimsByUri: byUri, + myPurchases: urlsForCurrentPage, + myPurchasesPageNumber: page, + myPurchasesPageTotalResults: totalItems, + fetchingMyPurchases: false + }); +}; + +reducers[PURCHASE_LIST_FAILED] = (state, action) => { + const { error } = action.data; + + return _extends$9({}, state, { + fetchingMyPurchases: false, + fetchingMyPurchasesError: error + }); +}; + function claimsReducer(state = defaultState, action) { const handler = reducers[action.type]; if (handler) return handler(state, action); @@ -6061,7 +6203,7 @@ const notificationsReducer = handleActions({ var _extends$e = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; -function _objectWithoutProperties$3(obj, keys) { var target = {}; for (var i in obj) { if (keys.indexOf(i) >= 0) continue; if (!Object.prototype.hasOwnProperty.call(obj, i)) continue; target[i] = obj[i]; } return target; } +function _objectWithoutProperties$4(obj, keys) { var target = {}; for (var i in obj) { if (keys.indexOf(i) >= 0) continue; if (!Object.prototype.hasOwnProperty.call(obj, i)) continue; target[i] = obj[i]; } return target; } const defaultState$6 = { editingURI: undefined, @@ -6118,7 +6260,7 @@ const publishReducer = handleActions({ publishSuccess: true }), [DO_PREPARE_EDIT]: (state, action) => { - const publishData = _objectWithoutProperties$3(action.data, []); + const publishData = _objectWithoutProperties$4(action.data, []); const { channel, name, uri } = publishData; // The short uri is what is presented to the user @@ -6879,6 +7021,7 @@ exports.doPreferenceGet = doPreferenceGet; exports.doPreferenceSet = doPreferenceSet; exports.doPrepareEdit = doPrepareEdit; exports.doPublish = doPublish; +exports.doPurchaseList = doPurchaseList; exports.doPurchaseUri = doPurchaseUri; exports.doRepost = doRepost; exports.doResetThumbnailStatus = doResetThumbnailStatus; @@ -6924,6 +7067,7 @@ exports.makeSelectClaimForUri = makeSelectClaimForUri; exports.makeSelectClaimIsMine = makeSelectClaimIsMine; exports.makeSelectClaimIsNsfw = makeSelectClaimIsNsfw; exports.makeSelectClaimIsPending = makeSelectClaimIsPending; +exports.makeSelectClaimWasPurchased = makeSelectClaimWasPurchased; exports.makeSelectClaimsInChannelForCurrentPageState = makeSelectClaimsInChannelForCurrentPageState; exports.makeSelectClaimsInChannelForPage = makeSelectClaimsInChannelForPage; exports.makeSelectCommentsForUri = makeSelectCommentsForUri; @@ -6946,6 +7090,7 @@ exports.makeSelectLoadingForUri = makeSelectLoadingForUri; exports.makeSelectMediaTypeForUri = makeSelectMediaTypeForUri; exports.makeSelectMetadataForUri = makeSelectMetadataForUri; exports.makeSelectMetadataItemForUri = makeSelectMetadataItemForUri; +exports.makeSelectMyPurchasesForPage = makeSelectMyPurchasesForPage; exports.makeSelectMyStreamUrlsForPage = makeSelectMyStreamUrlsForPage; exports.makeSelectNsfwCountForChannel = makeSelectNsfwCountForChannel; exports.makeSelectNsfwCountFromUris = makeSelectNsfwCountFromUris; @@ -7018,6 +7163,7 @@ exports.selectFetchingClaimSearch = selectFetchingClaimSearch; exports.selectFetchingClaimSearchByQuery = selectFetchingClaimSearchByQuery; exports.selectFetchingMyChannels = selectFetchingMyChannels; exports.selectFetchingMyClaimsPageError = selectFetchingMyClaimsPageError; +exports.selectFetchingMyPurchasesError = selectFetchingMyPurchasesError; exports.selectFetchingTxosError = selectFetchingTxosError; exports.selectFileInfosByOutpoint = selectFileInfosByOutpoint; exports.selectFileInfosDownloaded = selectFileInfosDownloaded; @@ -7032,6 +7178,7 @@ exports.selectHasTransactions = selectHasTransactions; exports.selectIsFetchingClaimListMine = selectIsFetchingClaimListMine; exports.selectIsFetchingFileList = selectIsFetchingFileList; exports.selectIsFetchingFileListDownloadedOrPublished = selectIsFetchingFileListDownloadedOrPublished; +exports.selectIsFetchingMyPurchases = selectIsFetchingMyPurchases; exports.selectIsFetchingTransactions = selectIsFetchingTransactions; exports.selectIsFetchingTxos = selectIsFetchingTxos; exports.selectIsResolvingPublishUris = selectIsResolvingPublishUris; @@ -7051,6 +7198,8 @@ exports.selectMyClaimsPageItemCount = selectMyClaimsPageItemCount; exports.selectMyClaimsPageNumber = selectMyClaimsPageNumber; exports.selectMyClaimsRaw = selectMyClaimsRaw; exports.selectMyClaimsWithoutChannels = selectMyClaimsWithoutChannels; +exports.selectMyPurchases = selectMyPurchases; +exports.selectMyPurchasesCount = selectMyPurchasesCount; exports.selectMyStreamUrlsCount = selectMyStreamUrlsCount; exports.selectPendingById = selectPendingById; exports.selectPendingClaims = selectPendingClaims; diff --git a/dist/flow-typed/Claim.js b/dist/flow-typed/Claim.js index 8a6d8e8..c9732bd 100644 --- a/dist/flow-typed/Claim.js +++ b/dist/flow-typed/Claim.js @@ -3,12 +3,10 @@ declare type Claim = StreamClaim | ChannelClaim; declare type ChannelClaim = GenericClaim & { - is_channel_signature_valid?: boolean, // we may have signed channels in the future value: ChannelMetadata, }; declare type StreamClaim = GenericClaim & { - is_channel_signature_valid?: boolean, value: StreamMetadata, }; @@ -23,7 +21,8 @@ declare type GenericClaim = { decoded_claim: boolean, // Not available currently https://github.com/lbryio/lbry/issues/2044 timestamp?: number, // date of last transaction height: number, // block height the tx was confirmed - is_mine: boolean, + is_channel_signature_valid?: boolean, + is_my_output: true, name: string, normalized_name: string, // `name` normalized via unicode NFD spec, nout: number, // index number for an output of a tx @@ -34,6 +33,7 @@ declare type GenericClaim = { value_type: 'stream' | 'channel', signing_channel?: ChannelClaim, repost_channel_url?: string, + purchase_receipt?: PurchaseReceipt, meta: { activation_height: number, claims_in_channel?: number, @@ -121,3 +121,15 @@ declare type Fee = { currency: string, address: string, }; + +declare type PurchaseReceipt = { + address: string, + amount: string, + claim_id: string, + confirmations: number, + height: number, + nout: number, + timestamp: number, + txid: string, + type: 'purchase', +}; diff --git a/dist/flow-typed/Lbry.js b/dist/flow-typed/Lbry.js index 97e2f20..d7c0430 100644 --- a/dist/flow-typed/Lbry.js +++ b/dist/flow-typed/Lbry.js @@ -214,6 +214,22 @@ declare type StreamRepostOptions = { declare type StreamRepostResponse = GenericTxResponse; +declare type PurchaseListResponse = { + items: Array, + page: number, + page_size: number, + total_items: number, + total_pages: number, +}; + +declare type PurchaseListOptions = { + page: number, + page_size: number, + resolve: boolean, + claim_id?: string, + channel_id?: string, +}; + // // Types used in the generic Lbry object that is exported // @@ -253,6 +269,7 @@ declare type LbryTypes = { support_list: (params: {}) => Promise, support_abandon: (params: {}) => Promise, stream_repost: (params: StreamRepostOptions) => Promise, + purchase_list: (params: PurchaseListOptions) => Promise, // File fetching and manipulation file_list: (params: {}) => Promise, diff --git a/flow-typed/Claim.js b/flow-typed/Claim.js index 8a6d8e8..c9732bd 100644 --- a/flow-typed/Claim.js +++ b/flow-typed/Claim.js @@ -3,12 +3,10 @@ declare type Claim = StreamClaim | ChannelClaim; declare type ChannelClaim = GenericClaim & { - is_channel_signature_valid?: boolean, // we may have signed channels in the future value: ChannelMetadata, }; declare type StreamClaim = GenericClaim & { - is_channel_signature_valid?: boolean, value: StreamMetadata, }; @@ -23,7 +21,8 @@ declare type GenericClaim = { decoded_claim: boolean, // Not available currently https://github.com/lbryio/lbry/issues/2044 timestamp?: number, // date of last transaction height: number, // block height the tx was confirmed - is_mine: boolean, + is_channel_signature_valid?: boolean, + is_my_output: true, name: string, normalized_name: string, // `name` normalized via unicode NFD spec, nout: number, // index number for an output of a tx @@ -34,6 +33,7 @@ declare type GenericClaim = { value_type: 'stream' | 'channel', signing_channel?: ChannelClaim, repost_channel_url?: string, + purchase_receipt?: PurchaseReceipt, meta: { activation_height: number, claims_in_channel?: number, @@ -121,3 +121,15 @@ declare type Fee = { currency: string, address: string, }; + +declare type PurchaseReceipt = { + address: string, + amount: string, + claim_id: string, + confirmations: number, + height: number, + nout: number, + timestamp: number, + txid: string, + type: 'purchase', +}; diff --git a/flow-typed/Lbry.js b/flow-typed/Lbry.js index 97e2f20..d7c0430 100644 --- a/flow-typed/Lbry.js +++ b/flow-typed/Lbry.js @@ -214,6 +214,22 @@ declare type StreamRepostOptions = { declare type StreamRepostResponse = GenericTxResponse; +declare type PurchaseListResponse = { + items: Array, + page: number, + page_size: number, + total_items: number, + total_pages: number, +}; + +declare type PurchaseListOptions = { + page: number, + page_size: number, + resolve: boolean, + claim_id?: string, + channel_id?: string, +}; + // // Types used in the generic Lbry object that is exported // @@ -253,6 +269,7 @@ declare type LbryTypes = { support_list: (params: {}) => Promise, support_abandon: (params: {}) => Promise, stream_repost: (params: StreamRepostOptions) => Promise, + purchase_list: (params: PurchaseListOptions) => Promise, // File fetching and manipulation file_list: (params: {}) => Promise, diff --git a/src/constants/action_types.js b/src/constants/action_types.js index cbaad87..dc3e0e6 100644 --- a/src/constants/action_types.js +++ b/src/constants/action_types.js @@ -126,6 +126,9 @@ export const ADD_FILES_REFLECTING = 'ADD_FILES_REFLECTING'; export const UPDATE_FILES_REFLECTING = 'UPDATE_FILES_REFLECTING'; export const TOGGLE_CHECKING_REFLECTING = 'TOGGLE_CHECKING_REFLECTING'; export const TOGGLE_CHECKING_PENDING = 'TOGGLE_CHECKING_PENDING'; +export const PURCHASE_LIST_STARTED = 'PURCHASE_LIST_STARTED'; +export const PURCHASE_LIST_COMPLETED = 'PURCHASE_LIST_COMPLETED'; +export const PURCHASE_LIST_FAILED = 'PURCHASE_LIST_FAILED'; // Comments export const COMMENT_LIST_STARTED = 'COMMENT_LIST_STARTED'; diff --git a/src/index.js b/src/index.js index a661d00..7fb59be 100644 --- a/src/index.js +++ b/src/index.js @@ -75,6 +75,7 @@ export { doRepost, doClearRepostError, doCheckPublishNameAvailability, + doPurchaseList, } from 'redux/actions/claims'; export { doDeletePurchasedUri, doPurchaseUri, doFileGet } from 'redux/actions/file'; @@ -213,6 +214,8 @@ export { makeSelectCanonicalUrlForUri, makeSelectPermanentUrlForUri, makeSelectSupportsForUri, + makeSelectMyPurchasesForPage, + makeSelectClaimWasPurchased, selectPendingById, selectReflectingById, selectClaimsById, @@ -253,6 +256,10 @@ export { selectMyClaimsPageNumber, selectMyClaimsPageItemCount, selectFetchingMyClaimsPageError, + selectMyPurchases, + selectIsFetchingMyPurchases, + selectFetchingMyPurchasesError, + selectMyPurchasesCount, } from 'redux/selectors/claims'; export { makeSelectCommentsForUri } from 'redux/selectors/comments'; diff --git a/src/lbry.js b/src/lbry.js index 61a893b..51f4111 100644 --- a/src/lbry.js +++ b/src/lbry.js @@ -111,6 +111,7 @@ const Lbry: LbryTypes = { transaction_list: (params = {}) => daemonCallWithResult('transaction_list', params), utxo_release: (params = {}) => daemonCallWithResult('utxo_release', params), support_abandon: (params = {}) => daemonCallWithResult('support_abandon', params), + purchase_list: (params = {}) => daemonCallWithResult('purchase_list', params), sync_hash: (params = {}) => daemonCallWithResult('sync_hash', params), sync_apply: (params = {}) => daemonCallWithResult('sync_apply', params), diff --git a/src/redux/actions/claims.js b/src/redux/actions/claims.js index 99e1d1d..c42a984 100644 --- a/src/redux/actions/claims.js +++ b/src/redux/actions/claims.js @@ -35,7 +35,9 @@ export function doResolveUris(uris: Array, returnCachedClaims: boolean = return; } - const options: { include_is_my_output?: boolean } = {}; + const options: { include_is_my_output?: boolean, include_purchase_receipt: boolean } = { + include_purchase_receipt: true, + }; if (urisToResolve.length === 1) { options.include_is_my_output = true; @@ -318,6 +320,7 @@ export function doFetchClaimsByChannel(uri: string, page: number = 1) { page: page || 1, order_by: ['release_time'], include_is_my_output: true, + include_purchase_receipt: true, }).then((result: ClaimSearchResponse) => { const { items: claims, total_items: claimsInChannel, page: returnedPage } = result; @@ -554,7 +557,10 @@ export function doClaimSearch( }); }; - Lbry.claim_search(options).then(success, failure); + Lbry.claim_search({ + ...options, + include_purchase_receipt: true, + }).then(success, failure); }; } @@ -623,3 +629,35 @@ export function doClearRepostError() { type: ACTIONS.CLEAR_REPOST_ERROR, }; } + +export function doPurchaseList(page: number = 1, pageSize: number = 99999) { + return (dispatch: Dispatch) => { + dispatch({ + type: ACTIONS.PURCHASE_LIST_STARTED, + }); + + const success = (result: PurchaseListResponse) => { + return dispatch({ + type: ACTIONS.PURCHASE_LIST_COMPLETED, + data: { + result, + }, + }); + }; + + const failure = error => { + dispatch({ + type: ACTIONS.PURCHASE_LIST_FAILED, + data: { + error: error.message, + }, + }); + }; + + Lbry.purchase_list({ + page: page, + page_size: pageSize, + resolve: true, + }).then(success, failure); + }; +} diff --git a/src/redux/reducers/claims.js b/src/redux/reducers/claims.js index ee9e85c..207c955 100644 --- a/src/redux/reducers/claims.js +++ b/src/redux/reducers/claims.js @@ -25,6 +25,11 @@ type State = { fetchingChannelClaims: { [string]: number }, fetchingMyChannels: boolean, fetchingClaimSearchByQuery: { [string]: boolean }, + myPurchases: ?Array, + myPurchasesPageNumber: ?number, + myPurchasesPageTotalResults: ?number, + fetchingMyPurchases: boolean, + fetchingMyPurchasesError: ?string, claimSearchByQuery: { [string]: Array }, claimSearchByQueryLastPageReached: { [string]: Array }, creatingChannel: boolean, @@ -59,10 +64,13 @@ const defaultState = { channelClaimCounts: {}, fetchingChannelClaims: {}, resolvingUris: [], - // This should not be a Set - // Storing sets in reducers can cause issues myChannelClaims: undefined, myClaims: undefined, + myPurchases: undefined, + myPurchasesPageNumber: undefined, + myPurchasesPageTotalResults: undefined, + fetchingMyPurchases: false, + fetchingMyPurchasesError: undefined, fetchingMyChannels: false, abandoningById: {}, pendingById: {}, @@ -83,6 +91,7 @@ const defaultState = { myClaimsPageNumber: undefined, myClaimsPageTotalResults: undefined, isFetchingClaimListMine: false, + isFetchingMyPurchases: false, isCheckingNameForPublish: false, checkingPending: false, checkingReflecting: false, @@ -189,13 +198,13 @@ reducers[ACTIONS.FETCH_CLAIM_LIST_MINE_COMPLETED] = (state: State, action: any): const byUri = Object.assign({}, state.claimsByUri); const pendingById: { [string]: Claim } = Object.assign({}, state.pendingById); let myClaimIds = new Set(state.myClaims); - let urlPage = []; + let urlsForCurrentPage = []; claims.forEach((claim: Claim) => { const uri = buildURI({ streamName: claim.name, streamClaimId: claim.claim_id }); const { claim_id: claimId } = claim; if (claim.type && claim.type.match(/claim|update/)) { - urlPage.push(uri); + urlsForCurrentPage.push(uri); if (claim.confirmations < 1) { pendingById[claimId] = claim; delete byId[claimId]; @@ -228,7 +237,7 @@ reducers[ACTIONS.FETCH_CLAIM_LIST_MINE_COMPLETED] = (state: State, action: any): byId, claimsByUri: byUri, pendingById, - myClaimsPageResults: urlPage, + myClaimsPageResults: urlsForCurrentPage, myClaimsPageNumber: page, myClaimsPageTotalResults: totalItems, }); @@ -374,6 +383,7 @@ reducers[ACTIONS.UPDATE_PENDING_CLAIMS] = (state: State, action: any): State => const pendingById: { [string]: Claim } = Object.assign({}, state.pendingById); let myClaimIds = new Set(state.myClaims); + // $FlowFixMe claims.forEach((claim: Claim) => { const uri = buildURI({ streamName: claim.name, streamClaimId: claim.claim_id }); const { claim_id: claimId } = claim; @@ -637,6 +647,59 @@ reducers[ACTIONS.TOGGLE_CHECKING_PENDING] = (state: State, action): State => { }); }; +reducers[ACTIONS.PURCHASE_LIST_STARTED] = (state: State): State => { + return { + ...state, + fetchingMyPurchases: true, + fetchingMyPurchasesError: null, + }; +}; + +reducers[ACTIONS.PURCHASE_LIST_COMPLETED] = (state: State, action: any): State => { + const { result }: { result: PurchaseListResponse, resolve: boolean } = action.data; + const page = result.page; + const totalItems = result.total_items; + + let byId = Object.assign({}, state.byId); + let byUri = Object.assign({}, state.claimsByUri); + let urlsForCurrentPage = []; + + result.items.forEach(item => { + if (!item.claim) { + // Abandoned claim + return; + } + + const { claim, ...purchaseInfo } = item; + claim.purchase_receipt = purchaseInfo; + const claimId = claim.claim_id; + const uri = claim.canonical_url; + + byId[claimId] = claim; + byUri[uri] = claimId; + urlsForCurrentPage.push(uri); + }); + + return Object.assign({}, state, { + byId, + claimsByUri: byUri, + myPurchases: urlsForCurrentPage, + myPurchasesPageNumber: page, + myPurchasesPageTotalResults: totalItems, + fetchingMyPurchases: false, + }); +}; + +reducers[ACTIONS.PURCHASE_LIST_FAILED] = (state: State, action: any): State => { + const { error } = action.data; + + return { + ...state, + fetchingMyPurchases: false, + fetchingMyPurchasesError: error, + }; +}; + export function claimsReducer(state: State = defaultState, action: any) { const handler = reducers[action.type]; if (handler) return handler(state, action); diff --git a/src/redux/selectors/claims.js b/src/redux/selectors/claims.js index 78b5a71..b38731c 100644 --- a/src/redux/selectors/claims.js +++ b/src/redux/selectors/claims.js @@ -6,9 +6,10 @@ import { } from 'redux/selectors/search'; import { selectSupportsByOutpoint } from 'redux/selectors/wallet'; import { createSelector } from 'reselect'; -import { isClaimNsfw, createNormalizedClaimSearchKey } from 'util/claim'; +import { isClaimNsfw, createNormalizedClaimSearchKey, filterClaims } from 'util/claim'; import { getSearchQueryString } from 'util/query-params'; import { PAGE_SIZE } from 'constants/claim'; + const selectState = state => state.claims || {}; export const selectClaimsById = createSelector( @@ -225,6 +226,55 @@ export const makeSelectClaimIsMine = (rawUri: string) => { ); }; +export const selectMyPurchases = createSelector( + selectState, + state => state.myPurchases +); + +export const selectMyPurchasesCount = createSelector( + selectState, + state => state.myPurchasesPageTotalResults +); + +export const selectIsFetchingMyPurchases = createSelector( + selectState, + state => state.fetchingMyPurchases +); + +export const selectFetchingMyPurchasesError = createSelector( + selectState, + state => state.fetchingMyPurchasesError +); + +export const makeSelectMyPurchasesForPage = (query: ?string, page: number = 1) => + createSelector( + selectMyPurchases, + selectClaimsByUri, + (myPurchases: Array, claimsByUri: { [string]: Claim }) => { + if (!myPurchases) { + return undefined; + } + + const fileInfos = myPurchases.map(uri => claimsByUri[uri]); + const matchingFileInfos = filterClaims(fileInfos, query); + const start = (Number(page) - 1) * Number(PAGE_SIZE); + const end = Number(page) * Number(PAGE_SIZE); + return matchingFileInfos && matchingFileInfos.length + ? matchingFileInfos + .slice(start, end) + .map(fileInfo => fileInfo.canonical_url || fileInfo.permanent_url) + : []; + } + ); + +export const makeSelectClaimWasPurchased = (uri: string) => + createSelector( + makeSelectClaimForUri(uri), + claim => { + return claim && claim.purchase_receipt !== undefined; + } + ); + export const selectAllFetchingChannelClaims = createSelector( selectState, state => state.fetchingChannelClaims || {} @@ -318,8 +368,8 @@ export const makeSelectDateForUri = (uri: string) => (claim.value.release_time ? claim.value.release_time * 1000 : claim.meta && claim.meta.creation_timestamp - ? claim.meta.creation_timestamp * 1000 - : null); + ? claim.meta.creation_timestamp * 1000 + : null); if (!timestamp) { return undefined; } @@ -694,7 +744,7 @@ export const makeSelectSupportsForUri = (uri: string) => selectSupportsByOutpoint, makeSelectClaimForUri(uri), (byOutpoint, claim: ?StreamClaim) => { - if (!claim || !claim.is_mine) { + if (!claim || !claim.is_my_output) { return null; } diff --git a/src/redux/selectors/file_info.js b/src/redux/selectors/file_info.js index 1ff96d5..53fcc96 100644 --- a/src/redux/selectors/file_info.js +++ b/src/redux/selectors/file_info.js @@ -212,6 +212,7 @@ function filterFileInfos(fileInfos, query) { const queryMatchRegExp = new RegExp(query, 'i'); return fileInfos.filter(fileInfo => { const { metadata } = fileInfo; + return ( (metadata.title && metadata.title.match(queryMatchRegExp)) || (fileInfo.channel_name && fileInfo.channel_name.match(queryMatchRegExp)) || @@ -233,12 +234,12 @@ export const makeSelectSearchDownloadUrlsForPage = (query, page = 1) => return matchingFileInfos && matchingFileInfos.length ? matchingFileInfos.slice(start, end).map(fileInfo => - buildURI({ - streamName: fileInfo.claim_name, - channelName: fileInfo.channel_name, - channelClaimId: fileInfo.channel_claim_id, - }) - ) + buildURI({ + streamName: fileInfo.claim_name, + channelName: fileInfo.channel_name, + channelClaimId: fileInfo.channel_claim_id, + }) + ) : []; } ); diff --git a/src/util/claim.js b/src/util/claim.js index cfde696..aa685f8 100644 --- a/src/util/claim.js +++ b/src/util/claim.js @@ -51,3 +51,20 @@ export function concatClaims( return claims; } + +export function filterClaims(claims: Array, query: ?string): Array { + if (query) { + const queryMatchRegExp = new RegExp(query, 'i'); + return claims.filter(claim => { + const { value } = claim; + + return ( + (value.title && value.title.match(queryMatchRegExp)) || + (claim.signing_channel && claim.signing_channel.name.match(queryMatchRegExp)) || + (claim.name && claim.name.match(queryMatchRegExp)) + ); + }); + } + + return claims; +}