Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 69 additions & 0 deletions src/app/models/access-role.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import {
ARCHIVE_MEMBERSHIP_ROLE_TO_ACCESS_ROLE,
getAccessRoleFromArchiveMembershipRole,
getAccessAsEnum,
type ArchiveMembershipRoleType,
} from './access-role';

describe('getAccessRoleFromArchiveMembershipRole', () => {
const expectedTranslations: Array<[ArchiveMembershipRoleType, string]> = [
['contributor', 'access.role.contributor'],
['curator', 'access.role.curator'],
['editor', 'access.role.editor'],
['manager', 'access.role.manager'],
['owner', 'access.role.owner'],
['viewer', 'access.role.viewer'],
];

expectedTranslations.forEach(
([archiveMembershipRole, expectedAccessRole]) => {
it(`should translate ${archiveMembershipRole}`, () => {
expect(
getAccessRoleFromArchiveMembershipRole(archiveMembershipRole),
).toBe(expectedAccessRole);
});
},
);

it('should cover every role Stela can send', () => {
expect(Object.keys(ARCHIVE_MEMBERSHIP_ROLE_TO_ACCESS_ROLE).length).toBe(
expectedTranslations.length,
);
});

it('should produce roles that getAccessAsEnum understands', () => {
expectedTranslations.forEach(([archiveMembershipRole]) => {
const accessRole = getAccessRoleFromArchiveMembershipRole(
archiveMembershipRole,
);

expect(getAccessAsEnum(accessRole)).toBeDefined();
});
});

it('should translate manager to manager, not the curator that PERMISSIONS_LEVEL_TO_ACCESS_ROLE maps it to', () => {
expect(getAccessRoleFromArchiveMembershipRole('manager')).toBe(
'access.role.manager',
);
});

it('should return undefined rather than guess at an unknown role', () => {
expect(
getAccessRoleFromArchiveMembershipRole(
'wizard' as ArchiveMembershipRoleType,
),
).toBeUndefined();
});

it('should return undefined when no role is given', () => {
expect(getAccessRoleFromArchiveMembershipRole(undefined)).toBeUndefined();
});

it('should return undefined for an inherited object property name', () => {
expect(
getAccessRoleFromArchiveMembershipRole(
'toString' as ArchiveMembershipRoleType,
),
).toBeUndefined();
});
});
40 changes: 40 additions & 0 deletions src/app/models/access-role.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,46 @@ export type PermissionsLevel =
| 'owner'
| 'viewer';

/** Mirrors Stela's ArchiveMembershipRole enum */
export type ArchiveMembershipRoleType =
| 'contributor'
| 'curator'
| 'editor'
| 'manager'
| 'owner'
| 'viewer';

export const ARCHIVE_MEMBERSHIP_ROLE_TO_ACCESS_ROLE: Record<
ArchiveMembershipRoleType,
AccessRoleType
> = {
contributor: 'access.role.contributor',
curator: 'access.role.curator',
editor: 'access.role.editor',
manager: 'access.role.manager',
owner: 'access.role.owner',
viewer: 'access.role.viewer',
};

function isArchiveMembershipRole(
value: unknown,
): value is ArchiveMembershipRoleType {
return (
typeof value === 'string' &&
Object.hasOwn(ARCHIVE_MEMBERSHIP_ROLE_TO_ACCESS_ROLE, value)
);
}

export function getAccessRoleFromArchiveMembershipRole(
archiveMembershipRole: ArchiveMembershipRoleType | undefined,
): AccessRoleType | undefined {
if (!isArchiveMembershipRole(archiveMembershipRole)) {
return undefined;
}

return ARCHIVE_MEMBERSHIP_ROLE_TO_ACCESS_ROLE[archiveMembershipRole];
}
Comment thread
aasandei-vsp marked this conversation as resolved.

// Mapping for share link permissions. Note the stela share link API
// mistakenly returns "manager" where it should use "curator" -- see
// https://github.com/PermanentOrg/stela/issues/540
Expand Down
62 changes: 62 additions & 0 deletions src/app/shared/services/api/folder.repo.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ const mockStelaFolder = {
downloadName: 'test-folder',
imageRatio: 1.5,
paths: { names: ['path1', 'path2'] },
accessRole: 'owner',
publicAt: null,
sort: 'name',
thumbnailUrls: {
Expand Down Expand Up @@ -395,4 +396,65 @@ describe('Folder repo', () => {
});
});
});

describe('access role translation', () => {
const convertFolder = async (overrides: Record<string, unknown>) => {
httpV2Spy.get.and.returnValue(
of([{ items: [{ ...mockStelaFolder, ...overrides }] }]),
);
const result = await folderRepo.getStelaFolderVOs([
new FolderVO({ folderId: 123 }),
]);
return result.getFolderVOs()[0];
};

it("should translate Stela's role into ours", async () => {
const folder = await convertFolder({ accessRole: 'owner' });

expect(folder.accessRole).toBe('access.role.owner');
});

it('should translate manager to manager, not curator', async () => {
const folder = await convertFolder({ accessRole: 'manager' });

expect(folder.accessRole).toBe('access.role.manager');
});

it('should leave the role undefined when Stela sends nothing', async () => {
const folder = await convertFolder({ accessRole: undefined });

expect(folder.accessRole).toBeUndefined();
});

it('should merge onto an existing folder without breaking its role', async () => {
const existingFolder = new FolderVO({
folderId: '123',
accessRole: 'access.role.owner',
});

existingFolder.update(await convertFolder({ accessRole: 'owner' }));

expect(existingFolder.accessRole).toBe('access.role.owner');
});

it('should translate the role on child folders too', async () => {
httpV2Spy.get.and.returnValues(
of([{ items: [mockStelaFolder] }]),
of([
{
items: [
{ ...mockStelaFolder, folderId: '999', accessRole: 'viewer' },
],
},
]),
);

const result = await folderRepo.getWithChildren([
new FolderVO({ folderId: 123 }),
]);
const child = result.getFolderVO(true).ChildItemVOs[0];

expect(child.accessRole).toBe('access.role.viewer');
});
});
});
6 changes: 6 additions & 0 deletions src/app/shared/services/api/folder.repo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@ import { FolderVO, FolderVOData, ItemVO } from '@root/app/models';
import { BaseResponse, BaseRepo } from '@shared/services/api/base';
import { firstValueFrom, Observable } from 'rxjs';
import { DataStatus } from '@models/data-status.enum';
import {
getAccessRoleFromArchiveMembershipRole,
type ArchiveMembershipRoleType,
} from '@models/access-role';
import { ShareLink } from '@root/app/share-links/models/share-link';
import {
convertStelaLocationToLocnVOData,
Expand Down Expand Up @@ -67,6 +71,7 @@ interface StelaFolder {
paths: {
names: string[];
};
accessRole: ArchiveMembershipRoleType;
publicAt: string;
sort: string;
thumbnailUrls?: {
Expand Down Expand Up @@ -117,6 +122,7 @@ const convertStelaFolderToFolderVO = (stelaFolder: StelaFolder): FolderVO => {
view: stelaFolder.view,
imageRatio: stelaFolder.imageRatio,
type: stelaFolder.type,
accessRole: getAccessRoleFromArchiveMembershipRole(stelaFolder.accessRole),
thumbStatus: stelaFolder.status,
thumbURL200: stelaFolder.thumbnailUrls?.['200'],
thumbURL500: stelaFolder.thumbnailUrls?.['500'],
Expand Down
42 changes: 42 additions & 0 deletions src/app/shared/services/api/record.repo.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -466,5 +466,47 @@ describe('RecordRepo', () => {
expect(record.thumbURL2000).toBe('https://example.com/2000');
expect(record.thumbnail256).toBe('https://example.com/256');
});

it("should translate Stela's role into ours", () => {
const record = convertStelaRecordToRecordVO({
...baseStelaRecord,
accessRole: 'viewer',
} as any);

expect(record.accessRole).toBe('access.role.viewer');
});

it('should translate manager to manager, not curator', () => {
const record = convertStelaRecordToRecordVO({
...baseStelaRecord,
accessRole: 'manager',
} as any);

expect(record.accessRole).toBe('access.role.manager');
});

it('should leave the role undefined when Stela sends nothing', () => {
const record = convertStelaRecordToRecordVO({
...baseStelaRecord,
} as any);

expect(record.accessRole).toBeUndefined();
});

it('should merge onto an existing record without breaking its role', () => {
const existingRecord = new RecordVO({
recordId: 42,
accessRole: 'access.role.curator',
});

existingRecord.update(
convertStelaRecordToRecordVO({
...baseStelaRecord,
accessRole: 'owner',
} as any),
);

expect(existingRecord.accessRole).toBe('access.role.owner');
});
});
});
10 changes: 8 additions & 2 deletions src/app/shared/services/api/record.repo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,11 @@ import { ThumbnailCache } from '@shared/utilities/thumbnail-cache/thumbnail-cach
import { firstValueFrom } from 'rxjs';
import { FileFormat, PermanentFile } from '@models/file-vo';
import { ShareStatus } from '@models/share-vo';
import { AccessRoleType } from '@models/access-role';
import {
AccessRoleType,
getAccessRoleFromArchiveMembershipRole,
type ArchiveMembershipRoleType,
} from '@models/access-role';
import { ShareLink } from '@root/app/share-links/models/share-link';
import { getFirst } from '../http-v2/http-v2.service';
import { CENTRAL_TIMEZONE_VO } from './folder.repo';
Expand Down Expand Up @@ -106,8 +110,9 @@ export interface StelaShare {
thumbURL200: string;
};
}
export type StelaRecord = Omit<RecordVO, 'files'> & {
export type StelaRecord = Omit<RecordVO, 'files' | 'accessRole'> & {
tags: Array<StelaTag> | null;
accessRole: ArchiveMembershipRoleType;
archiveNumber: string;
displayDate: string;
displayTime?: string;
Expand Down Expand Up @@ -214,6 +219,7 @@ export const convertStelaRecordToRecordVO = (
convertStelaTagToTagVO(stelaTag, stelaRecord.archiveId),
),
archiveNbr: stelaRecord.archiveNumber,
accessRole: getAccessRoleFromArchiveMembershipRole(stelaRecord.accessRole),
displayDT: stelaRecord.displayDate,
displayTime: stelaRecord.displayTime,
folder_linkId: Number.parseInt(stelaRecord.folderLinkId, 10),
Expand Down
Loading