-
Notifications
You must be signed in to change notification settings - Fork 11
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
♻️ front: moved driveitem access entities editing to a helper file (#50)
- Loading branch information
1 parent
73e2bb5
commit 9a7dce8
Showing
10 changed files
with
225 additions
and
131 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
167 changes: 167 additions & 0 deletions
167
tdrive/frontend/src/app/features/files/utils/access-info-helpers.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,167 @@ | ||
import { | ||
AuthEntity, | ||
DriveItemAccessInfo, | ||
DriveFileAccessLevel, | ||
DriveFileAccessLevelForInherited, | ||
DriveFileAccessLevelForPublicLink, | ||
DriveItem, | ||
} from '@features/drive/types'; | ||
|
||
const entityMatcher = (entityType: AuthEntity['type'], entityId?: string) => | ||
(entity: AuthEntity) => | ||
entity && entity.type === entityType && (entityId == undefined || entity.id === entityId); | ||
|
||
/** Updates the level of entities matching the given type and id, or adds the return | ||
* of updater with no argument. | ||
* | ||
* If an item is not unique, all are modified and a console warning is printed except for `entityType` "channel". | ||
*/ | ||
function upsertEntities( | ||
entities: AuthEntity[] | undefined, | ||
entityType: AuthEntity['type'], | ||
entityId: string | undefined, | ||
level: DriveFileAccessLevel, | ||
) { | ||
const updater = (existing?: AuthEntity) => { | ||
if (!existing && !entityId) throw new Error("Cannot create entry without entityId"); | ||
return { | ||
...(existing || { type: entityType, id: entityId }), | ||
level, | ||
}; | ||
}; | ||
let found = false; | ||
if (!entities || entities.length == 0) return [updater()]; | ||
const matcher = entityMatcher(entityType, entityId); | ||
const mapped = entities.map(item => { | ||
if (!matcher(item)) return item; | ||
if (found && entityType != "channel") console.warn(`DriveItem has more than one access_info entry for '${entityType}' id = ${entityId}:`, item); | ||
found = true; | ||
return updater(item); | ||
}).filter(x => x); | ||
if (found) return mapped; | ||
return [...entities, updater()]; | ||
} | ||
|
||
/** If `level == false`, deletes matching entries, otherwise calls `upsertEntities` | ||
*/ | ||
function editEntities( | ||
entities: AuthEntity[] | undefined, | ||
entityType: AuthEntity['type'], | ||
entityId: string | undefined, | ||
level: DriveFileAccessLevel | false, | ||
) { | ||
if (level) return upsertEntities(entities, entityType, entityId, level); | ||
const matcher = entityMatcher(entityType, entityId); | ||
return entities && entities.filter(item => !matcher(item)); | ||
} | ||
|
||
/** Return a list of all DriveItemAccessInfo for entities with the provided type sorted by id */ | ||
const accessInfosOfEntitiesOfType = (item: DriveItem | undefined, entityType: AuthEntity['type']) => | ||
item?.access_info.entities.filter(entityMatcher(entityType)).sort((a, b) => a.id.localeCompare(b.id)) || []; | ||
|
||
/** Find access level for the given entity in the DriveItem's access_info and return it's level if any, or undefined */ | ||
export function accessLevelOfEntityForItem( | ||
item: DriveItem | undefined, | ||
entityType: AuthEntity['type'], | ||
entityId: string, | ||
) { | ||
const matcher = entityMatcher(entityType, entityId); | ||
return item?.access_info.entities.filter(matcher)[0]?.level; | ||
} | ||
|
||
/** Return the item with the access right for the given entity added or changed to level; or if its false, remove if any */ | ||
const itemWithEntityAccessChanged = ( | ||
item: DriveItem, | ||
entityType: AuthEntity['type'], | ||
entityId: string | undefined, | ||
level: DriveFileAccessLevel | false, | ||
) => ({ | ||
...item, | ||
access_info: { | ||
...item.access_info, | ||
entities: editEntities(item.access_info.entities, entityType, entityId, level), | ||
}, | ||
} as DriveItem); | ||
|
||
// Note this just assumes uniqueness and just logs in the console if that is not the case and uses the first one. | ||
const getAccessLevelOfUniqueForType = (item: DriveItem | undefined, entityType: AuthEntity['type'], entityId?: string) => { | ||
if (!item) return undefined; | ||
const accesses = item.access_info.entities.filter(entityMatcher(entityType, entityId)); | ||
if (accesses.length != 1 && entityType != "channel") | ||
console.warn(`DriveItem doesn't have exactly one access_info entry for '${entityType}${entityId ? " id: " + entityId : ""}':`, item); | ||
return accesses[0]?.level; | ||
} | ||
|
||
/** Return a shallow copy of item with the access right for the given user added or changed to level; or if its false, removed */ | ||
export const changeUserAccess = (item: DriveItem, userId: string, level: DriveFileAccessLevel | false) => | ||
itemWithEntityAccessChanged(item, "user", userId, level); | ||
|
||
/** Return a list of all DriveItemAccessInfo for users sorted by id */ | ||
export const getAllUserAccesses = (item: DriveItem) => accessInfosOfEntitiesOfType(item, "user"); | ||
|
||
/** Return the access level for the provided user; an entry is expected to exist (or there is a `console.warn`) */ | ||
export const getUserAccessLevel = (item: DriveItem, userId: string) => getAccessLevelOfUniqueForType(item, "user", userId); | ||
|
||
|
||
|
||
/** Return current access level inherited from parent folder by item */ | ||
export const getInheritedAccessLevel = (item?: DriveItem) => getAccessLevelOfUniqueForType(item, "folder"); | ||
|
||
/** Return a shallow copy of item with the access right for the given entity added or changed to level; or if its false, removed */ | ||
export const changeInheritedAccess = (item: DriveItem, level: DriveFileAccessLevelForInherited | false) => | ||
itemWithEntityAccessChanged(item, "folder", "parent", level); | ||
|
||
|
||
|
||
/** Return access level for the company type entity if any */ | ||
export const getCompanyAccessLevel = (item?: DriveItem) => getAccessLevelOfUniqueForType(item, "company"); | ||
|
||
/** Return a shallow copy of item with the access right for the given entity changed to level; or if its false, removed. | ||
* Removing is irreversible. Calling this method setting a level on a `DriveItem` that doesn't have a company entry will | ||
* throw an Error. | ||
*/ | ||
export const changeCompanyAccessLevel = (item: DriveItem, level: DriveFileAccessLevel | false) => | ||
itemWithEntityAccessChanged(item, "company", undefined, level); | ||
|
||
|
||
|
||
/** Return the first, if any, access level of a channel */ | ||
export const getFirstChannelAccessLevel = (item?: DriveItem) => getAccessLevelOfUniqueForType(item, "channel"); | ||
|
||
/** Return a shallow copy of item with the access right for the given entity changed to level; or if its false, removed. | ||
* Removing is irreversible. Calling this method setting a level on a `DriveItem` that doesn't have a channel entry will | ||
* throw an Error. | ||
*/ | ||
export const changeAllChannelAccessLevels = (item: DriveItem, level: DriveFileAccessLevel | false) => | ||
itemWithEntityAccessChanged(item, "channel", undefined, level); | ||
|
||
|
||
|
||
/** Return a shallow copy of item with the public link properties for the given entity changed | ||
* | ||
* - level defaults to 'none' if not set and this is called without a value in `changes` for it | ||
* - sets all fields in `changes` argument as they are, even if their value is null, undefined, false etc | ||
* - runtime does not validate that fields in `changes` argument are properly typed or correctly named | ||
*/ | ||
export function changePublicLink( | ||
item: DriveItem, | ||
changes: { | ||
level?: DriveFileAccessLevelForPublicLink, | ||
expiration?: number, | ||
password?: string, | ||
}, | ||
) { | ||
const publicSettings : DriveItemAccessInfo["public"] = item.access_info.public ? { ...item.access_info.public } : { token: '', level: "none" }; | ||
Object.entries(changes).forEach(([field, value]) => (publicSettings as { [k: string]: unknown })[field] = value); | ||
return { | ||
...item, | ||
access_info: { | ||
...item.access_info, | ||
public: publicSettings, | ||
}, | ||
} as DriveItem; | ||
} | ||
|
||
/** Return the access level of the public link if it's set, and it isn't `"none"`, or returns false */ | ||
export const hasAnyPublicLinkAccess = (item?: Partial<DriveItem>) : Exclude<DriveFileAccessLevel, "none"> | false => | ||
(item?.access_info?.public?.level && item.access_info.public.level != "none") ? item.access_info.public.level : false; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.