Skip to content

Commit d09dc29

Browse files
committed
Merge branch 'feat-sort-assets-in-VERIFIED-tab-by-uploaded_at' into v240104-capture-cam-ionic-launch
2 parents d9b5652 + dbd0354 commit d09dc29

File tree

6 files changed

+79
-1
lines changed

6 files changed

+79
-1
lines changed

src/app/features/home/capture-tab/capture-tab.component.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,9 @@ export class CaptureTabComponent implements OnInit {
9595
);
9696

9797
readonly captures$ = this.proofs$.pipe(
98-
map(proofs => proofs.sort((a, b) => b.timestamp - a.timestamp))
98+
map(proofs =>
99+
proofs.sort((a, b) => b.uploadedAtOrTimestamp - a.uploadedAtOrTimestamp)
100+
)
99101
);
100102

101103
readonly networkConnected$ = this.networkService.connected$;

src/app/shared/dia-backend/asset/dia-backend-asset-repository.service.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -357,6 +357,7 @@ export interface DiaBackendAsset extends Tuple {
357357
readonly caption: string;
358358
readonly post_creation_workflow_id: string;
359359
readonly mint_workflow_id: string;
360+
readonly uploaded_at: string;
360361
}
361362

362363
export interface OwnerAddresses extends Tuple {

src/app/shared/dia-backend/asset/downloading/dia-backend-downloading.service.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ export class DiaBackendAssetDownloadingService {
8181
});
8282
proof.diaBackendAssetId = diaBackendAsset.id;
8383
proof.caption = diaBackendAsset.caption;
84+
proof.uploadedAt = diaBackendAsset.uploaded_at;
8485
if (diaBackendAsset.signed_metadata) proof.setSignatureVersion();
8586
return this.proofRepository.add(proof, OnConflictStrategy.REPLACE);
8687
}

src/app/shared/dia-backend/asset/uploading/dia-backend-asset-uploading.service.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,7 @@ export class DiaBackendAssetUploadingService {
145145
}),
146146
map(diaBackendAsset => {
147147
proof.diaBackendAssetId = diaBackendAsset.id;
148+
proof.uploadedAt = diaBackendAsset.uploaded_at;
148149
return proof;
149150
}),
150151
retryWhen(err$ =>

src/app/shared/repositories/proof/proof.spec.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,43 @@ describe('Proof utils', () => {
287287
const expected = `{\n "asset_mime_type": "${ASSET1_MIMETYPE}",\n "caption": "",\n "created_at": ${TIMESTAMP},\n "device_name": "${DEVICE_NAME_VALUE1}",\n "information": {\n "device.device_name": "${DEVICE_NAME_VALUE2}",\n "device.humidity": 0.8,\n "geolocation.geolocation_latitude": ${GEOLOCATION_LATITUDE2},\n "geolocation.geolocation_longitude": ${GEOLOCATION_LONGITUDE2}\n },\n "location_latitude": ${GEOLOCATION_LATITUDE1},\n "location_longitude": ${GEOLOCATION_LONGITUDE1},\n "proof_hash": "${ASSET1_SHA256SUM}",\n "recorder": "Capture",\n "spec_version": "2.0.0"\n}`;
288288
expect(getSerializedSortedProofMetadata(ProofMetadata)).toEqual(expected);
289289
});
290+
291+
describe('uploadedAtOrTimestamp', () => {
292+
it('should return timestamp in milliseconds when uploadedAt is undefined', async () => {
293+
proof = await Proof.from(mediaStore, ASSETS, TRUTH, SIGNATURES_VALID);
294+
expect(proof.uploadedAtOrTimestamp).toEqual(TRUTH.timestamp);
295+
});
296+
297+
it('should return uploadedAt in milliseconds when uploadedAt is defined', async () => {
298+
const date = '2023-12-21T01:15:17Z'; // sample returned by API
299+
const dateInMilliseconds = Date.parse(date);
300+
301+
proof = await Proof.from(mediaStore, ASSETS, TRUTH, SIGNATURES_VALID);
302+
proof.uploadedAt = date;
303+
304+
expect(proof.uploadedAtOrTimestamp).toEqual(dateInMilliseconds);
305+
});
306+
307+
it('should return timestamp in milliseconds when uploadedAt is not a valid date', async () => {
308+
proof = await Proof.from(mediaStore, ASSETS, TRUTH, SIGNATURES_VALID);
309+
proof.uploadedAt = 'invalid date';
310+
expect(proof.uploadedAtOrTimestamp).toEqual(TRUTH.timestamp);
311+
});
312+
313+
it('should return timestamp in milliseconds when its in seconds', async () => {
314+
const timestamp = 1627545600; // 29th July 2021 12:00:00 GMT
315+
const timestampInMilliseconds = timestamp * 1000;
316+
317+
proof = await Proof.from(
318+
mediaStore,
319+
ASSETS,
320+
{ ...TRUTH, timestamp },
321+
SIGNATURES_VALID
322+
);
323+
324+
expect(proof.uploadedAtOrTimestamp).toEqual(timestampInMilliseconds);
325+
});
326+
});
290327
});
291328

292329
const ASSET1_MIMETYPE: MimeType = 'image/png';

src/app/shared/repositories/proof/proof.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,13 @@ export class Proof {
3232
*/
3333
caption = '';
3434

35+
/**
36+
* The timestamp when the asset is uploaded to the backend, in the format "2023-12-21T01:15:17Z".
37+
* By default, it is undefined. Once the asset is successfully uploaded, the uploadedAt property
38+
* will be set to the timestamp provided by the backend.
39+
*/
40+
uploadedAt?: string = undefined;
41+
3542
isCollected = false;
3643

3744
signatures: Signatures = {};
@@ -47,6 +54,32 @@ export class Proof {
4754
*/
4855
cameraSource: CameraSource = CameraSource.Camera;
4956

57+
/**
58+
* Used to sort the assets in the VERIFIED tab either by timestamp or uploadedAt (if available).
59+
*/
60+
get uploadedAtOrTimestamp() {
61+
const MILLISECONDS_PER_SECOND = 1000;
62+
const LENGTH_IN_MILLISECONDS = 13;
63+
64+
// convert timestamp to milliseconds if needed
65+
const proofTimestampInMilliseconds =
66+
this.timestamp.toString().length === LENGTH_IN_MILLISECONDS
67+
? this.timestamp
68+
: this.timestamp * MILLISECONDS_PER_SECOND;
69+
70+
const serverTimestampInMilliseconds = Date.parse(this.uploadedAt ?? '');
71+
return serverTimestampInMilliseconds || proofTimestampInMilliseconds;
72+
}
73+
74+
/**
75+
* The timestamp when the proof was first created or captured. Different from uploadedAt
76+
* The timestamp is generated using Date.now() and is represented in milliseconds.
77+
*
78+
* Note: After restoring or syncing with the backend assets, the timestamp will be in seconds.
79+
* For more details, refer to https://github.com/numbersprotocol/storage-backend/issues/976
80+
*
81+
* Note: Milliseconds are 13 digits long, while seconds are 10 digits long.
82+
*/
5083
get timestamp() {
5184
return this.truth.timestamp;
5285
}
@@ -127,6 +160,7 @@ export class Proof {
127160
proof.setIndexedAssets(indexedProofView.indexedAssets);
128161
proof.diaBackendAssetId = indexedProofView.diaBackendAssetId;
129162
proof.caption = indexedProofView.caption ?? '';
163+
proof.uploadedAt = indexedProofView.uploadedAt;
130164
proof.isCollected = indexedProofView.isCollected ?? false;
131165
proof.signatureVersion = indexedProofView.signatureVersion;
132166
proof.integritySha = indexedProofView.integritySha;
@@ -298,6 +332,7 @@ export class Proof {
298332
signatureVersion: this.signatureVersion,
299333
diaBackendAssetId: this.diaBackendAssetId,
300334
caption: this.caption,
335+
uploadedAt: this.uploadedAt,
301336
isCollected: this.isCollected,
302337
integritySha: this.integritySha,
303338
cameraSource: this.cameraSource,
@@ -433,6 +468,7 @@ export interface IndexedProofView extends Tuple {
433468
readonly signatureVersion?: string;
434469
readonly diaBackendAssetId?: string;
435470
readonly caption?: string;
471+
readonly uploadedAt?: string;
436472
readonly isCollected?: boolean;
437473
readonly integritySha?: string;
438474
readonly cameraSource: CameraSource;

0 commit comments

Comments
 (0)