🤝 Shared with me (#71)
* 🚧 #46 Shared with me - added converter of DriveFile to dto with user information - updated version of jest to the latest for better compatibility with typescript and mocking class instances - added run configuration for unit tests Co-authored-by: Romaric Mourgues <rmourgues@linagora.com>
This commit is contained in:
@@ -89,4 +89,5 @@ type AuthEntity = {
|
||||
type: "user" | "channel" | "company" | "folder";
|
||||
id: string | "parent";
|
||||
level: publicAccessLevel | DriveFileAccessLevel;
|
||||
grantor: string;
|
||||
};
|
||||
|
||||
@@ -3,7 +3,7 @@ import _ from "lodash";
|
||||
import { logger } from "../../../core/platform/framework";
|
||||
import Repository from "../../../core/platform/services/database/services/orm/repository/repository";
|
||||
import globalResolver from "../../global-resolver";
|
||||
import { DriveFile } from "../entities/drive-file";
|
||||
import { AccessInformation, DriveFile } from "../entities/drive-file";
|
||||
import { CompanyExecutionContext, DriveFileAccessLevel } from "../types";
|
||||
|
||||
/**
|
||||
@@ -325,3 +325,22 @@ export const makeStandaloneAccessLevel = async (
|
||||
|
||||
return accessInfo;
|
||||
};
|
||||
|
||||
export const getSharedByUser = (
|
||||
accessInfo: AccessInformation,
|
||||
context: CompanyExecutionContext,
|
||||
): string => {
|
||||
// Try to find entity that matches user the most
|
||||
// at first user, and then company
|
||||
// we don't consider right now the access level by folder or channel or workspace
|
||||
for (const idx in accessInfo?.entities) {
|
||||
const entity = accessInfo.entities[idx];
|
||||
if (entity.type === "user" && entity.id === context.user?.id) {
|
||||
return entity.grantor;
|
||||
}
|
||||
if (entity.type === "company" && entity.id === context.company.id) {
|
||||
return entity.grantor;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
@@ -370,7 +370,14 @@ export class DocumentsService {
|
||||
oldParent = item.parent_id;
|
||||
}
|
||||
|
||||
if (key === "name") {
|
||||
if (key === "access_info") {
|
||||
item.access_info = content.access_info;
|
||||
item.access_info.entities.forEach(info => {
|
||||
if (!info.grantor) {
|
||||
info.grantor = context.user.id;
|
||||
}
|
||||
});
|
||||
} else if (key === "name") {
|
||||
item.name = await getItemName(
|
||||
content.parent_id || item.parent_id,
|
||||
item.id,
|
||||
@@ -885,6 +892,7 @@ export class DocumentsService {
|
||||
type: "channel",
|
||||
id: channelId,
|
||||
level: level === "write" ? "write" : "read",
|
||||
grantor: context.user.id,
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
@@ -47,6 +47,8 @@ export type SearchDocumentsOptions = {
|
||||
last_modified_gt?: number;
|
||||
last_modified_lt?: number;
|
||||
sort?: SortType;
|
||||
view?: string;
|
||||
fields?: string[];
|
||||
};
|
||||
|
||||
export type SearchDocumentsBody = {
|
||||
@@ -59,6 +61,8 @@ export type SearchDocumentsBody = {
|
||||
last_modified_gt?: number;
|
||||
last_modified_lt?: number;
|
||||
sort?: SortType;
|
||||
view?: string;
|
||||
fields?: string[];
|
||||
};
|
||||
|
||||
export type DocumentsMessageQueueRequest = {
|
||||
|
||||
@@ -61,16 +61,19 @@ export const getDefaultDriveItem = (
|
||||
id: "parent",
|
||||
type: "folder",
|
||||
level: "manage",
|
||||
grantor: null,
|
||||
},
|
||||
{
|
||||
id: item.company_id,
|
||||
type: "company",
|
||||
level: "none",
|
||||
grantor: null,
|
||||
},
|
||||
{
|
||||
id: context.user?.id,
|
||||
type: "user",
|
||||
level: "manage",
|
||||
grantor: null,
|
||||
},
|
||||
],
|
||||
public: {
|
||||
@@ -476,7 +479,7 @@ export const getItemName = async (
|
||||
* @param {string} source - the to be moved item id.
|
||||
* @param {string} target - the to be moved to item id.
|
||||
* @param {string} repository - the Drive item repository.
|
||||
* @param {CompanyExecutionContex} context - the execution context.
|
||||
* @param {CompanyExecutionContext} context - the execution context.
|
||||
* @returns {Promise<boolean>} - whether the move is possible or not.
|
||||
*/
|
||||
export const canMoveItem = async (
|
||||
@@ -531,7 +534,11 @@ export const canMoveItem = async (
|
||||
return true;
|
||||
};
|
||||
|
||||
export function isFileType(fileMime: string, fileName: string, requiredExtensions: string[]): any {
|
||||
export function isFileType(
|
||||
fileMime: string,
|
||||
fileName: string,
|
||||
requiredExtensions: string[],
|
||||
): boolean {
|
||||
const extension = fileName.split(".").pop();
|
||||
const secondaryExtensions = Object.keys(mimes).filter(k => mimes[k] === fileMime);
|
||||
const fileExtensions = [extension, ...secondaryExtensions];
|
||||
|
||||
@@ -18,8 +18,12 @@ import {
|
||||
SearchDocumentsBody,
|
||||
SearchDocumentsOptions,
|
||||
} from "../../types";
|
||||
import { DriveFileDTO } from "../dto/drive-file-dto";
|
||||
import { DriveFileDTOBuilder } from "../dto/drive-file-dto-builder";
|
||||
|
||||
export class DocumentsController {
|
||||
private driveFileDTOBuilder = new DriveFileDTOBuilder();
|
||||
|
||||
/**
|
||||
* Creates a DriveFile item
|
||||
*
|
||||
@@ -340,7 +344,7 @@ export class DocumentsController {
|
||||
Body: SearchDocumentsBody;
|
||||
Querystring: { public_token?: string };
|
||||
}>,
|
||||
): Promise<ListResult<DriveFile>> => {
|
||||
): Promise<ListResult<DriveFileDTO>> => {
|
||||
try {
|
||||
const context = getDriveExecutionContext(request);
|
||||
|
||||
@@ -353,7 +357,9 @@ export class DocumentsController {
|
||||
this.throw500Search();
|
||||
}
|
||||
|
||||
return await globalResolver.services.documents.documents.search(options, context);
|
||||
const fileList = await globalResolver.services.documents.documents.search(options, context);
|
||||
|
||||
return this.driveFileDTOBuilder.build(fileList, context, options.fields, options.view);
|
||||
} catch (error) {
|
||||
logger.error("error while searching for document", error);
|
||||
this.throw500Search();
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
import { DriveFile } from "../../entities/drive-file";
|
||||
import { DriveFileDTO } from "./drive-file-dto";
|
||||
import {
|
||||
ListResult,
|
||||
Paginable,
|
||||
Pagination,
|
||||
} from "../../../../core/platform/framework/api/crud-service";
|
||||
import _ from "lodash";
|
||||
import { CompanyExecutionContext } from "../../types";
|
||||
import globalResolver from "../../../../services/global-resolver";
|
||||
import User from "../../../../services/user/entities/user";
|
||||
import { getSharedByUser } from "../../services/access-check";
|
||||
|
||||
export class DriveFileDTOBuilder {
|
||||
private views: Map<string, string[]> = new Map([
|
||||
["default", Object.getOwnPropertyNames(new DriveFile())],
|
||||
[
|
||||
"shared_with_me",
|
||||
[
|
||||
"id",
|
||||
"name",
|
||||
"parent_id",
|
||||
"shared_by",
|
||||
"created_by",
|
||||
"is_directory",
|
||||
"extension",
|
||||
"added",
|
||||
"last_modified",
|
||||
"size",
|
||||
"last_version_cache",
|
||||
],
|
||||
],
|
||||
]);
|
||||
usersService = globalResolver.services?.users;
|
||||
|
||||
public async build(
|
||||
files: ListResult<DriveFile>,
|
||||
context: CompanyExecutionContext,
|
||||
fields?: string[],
|
||||
view?: string,
|
||||
): Promise<ListResult<DriveFileDTO>> {
|
||||
if (view) {
|
||||
fields = this.views.get(view);
|
||||
}
|
||||
if (!fields) {
|
||||
fields = this.views.get("default");
|
||||
}
|
||||
|
||||
//if we need to fetch users, find all the user identifiers ask theirs data at once
|
||||
const fileUsers = new Map<string, FileUsers>();
|
||||
// gather all user identifiers who share files with the current user
|
||||
if (fields.some(f => f == "shared_by")) {
|
||||
files.getEntities().forEach(f => {
|
||||
fileUsers.set(f.id, { grantor: getSharedByUser(f.access_info, context) });
|
||||
});
|
||||
}
|
||||
// gather all users who created the files
|
||||
if (fields.some(f => f == "created_by")) {
|
||||
files.getEntities().forEach(f => {
|
||||
if (!fileUsers.has(f.id)) fileUsers.set(f.id, { creator: f.creator });
|
||||
else fileUsers.get(f.id).creator = f.creator;
|
||||
});
|
||||
}
|
||||
if (fileUsers.size > 0) {
|
||||
await this.initUserCash(fileUsers);
|
||||
}
|
||||
|
||||
const nextPage: Paginable = new Pagination(files.page_token, files.nextPage?.limitStr);
|
||||
return new ListResult(
|
||||
"drive_files_dto",
|
||||
files.getEntities().map(f => this._buildDto(f, fields, fileUsers)),
|
||||
nextPage,
|
||||
);
|
||||
}
|
||||
|
||||
private async initUserCash(fileUsers: Map<string, FileUsers>): Promise<void> {
|
||||
const ids = [] as string[];
|
||||
fileUsers.forEach(f => {
|
||||
if (f.creator) ids.push(f.creator);
|
||||
if (f.grantor) ids.push(f.grantor);
|
||||
});
|
||||
|
||||
const users = await this.fetchUsers(ids);
|
||||
const userById = new Map(users.map(u => [u.id, u]));
|
||||
fileUsers.forEach(f => {
|
||||
f.creatorObj = userById.get(f.creator);
|
||||
f.grantorObj = userById.get(f.grantor);
|
||||
});
|
||||
}
|
||||
|
||||
private async fetchUsers(ids: string[]) {
|
||||
return (
|
||||
await this.usersService.list(
|
||||
{
|
||||
limitStr: ids.length.toString(),
|
||||
} as Pagination,
|
||||
{ userIds: _.uniq(ids) },
|
||||
)
|
||||
).getEntities();
|
||||
}
|
||||
|
||||
private _buildDto(file: DriveFile, fields: string[], userCache: Map<string, FileUsers>) {
|
||||
const dto = _.pick(file, fields) as DriveFileDTO;
|
||||
const users = userCache.get(file.id);
|
||||
if (users) {
|
||||
dto.created_by = users?.creatorObj;
|
||||
dto.shared_by = users?.grantorObj;
|
||||
}
|
||||
return dto;
|
||||
}
|
||||
}
|
||||
|
||||
type FileUsers = {
|
||||
creator?: string;
|
||||
grantor?: string;
|
||||
creatorObj?: User;
|
||||
grantorObj?: User;
|
||||
};
|
||||
@@ -0,0 +1,7 @@
|
||||
import { DriveFile } from "../../entities/drive-file";
|
||||
import User from "../../../user/entities/user";
|
||||
|
||||
export class DriveFileDTO extends DriveFile {
|
||||
created_by: User;
|
||||
shared_by: User;
|
||||
}
|
||||
Reference in New Issue
Block a user