@@ -0,0 +1,18 @@
|
|||||||
|
<component name="ProjectRunConfigurationManager">
|
||||||
|
<configuration default="false" name="Tests in e2e/" type="JavaScriptTestRunnerJest" nameIsGenerated="true">
|
||||||
|
<node-interpreter value="project" />
|
||||||
|
<jest-package value="$PROJECT_DIR$/tdrive/backend/node/node_modules/jest" />
|
||||||
|
<working-dir value="$PROJECT_DIR$/tdrive/backend/node" />
|
||||||
|
<jest-options value="--forceExit --coverage --detectOpenHandles --maxWorkers=1 --testTimeout=30000" />
|
||||||
|
<envs>
|
||||||
|
<env name="DB_DRIVER" value="mongodb" />
|
||||||
|
<env name="DB_MONGO_URI" value="mongodb://localhost:27017" />
|
||||||
|
<env name="PUBSUB_TYPE" value="local" />
|
||||||
|
<env name="SEARCH_DRIVER" value="elasticsearch" />
|
||||||
|
<env name="STORAGE_LOCAL_PATH" value="$USER_HOME$/dev/linagora/TDrive/tdrive/docker-data" />
|
||||||
|
</envs>
|
||||||
|
<scope-kind value="DIRECTORY" />
|
||||||
|
<test-directory value="$PROJECT_DIR$/tdrive/backend/node/test/e2e" />
|
||||||
|
<method v="2" />
|
||||||
|
</configuration>
|
||||||
|
</component>
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
<component name="ProjectRunConfigurationManager">
|
||||||
|
<configuration default="false" name="the Drive feature.did search a file shared by another user" type="JavaScriptTestRunnerJest" nameIsGenerated="true">
|
||||||
|
<node-interpreter value="project" />
|
||||||
|
<jest-package value="$PROJECT_DIR$/tdrive/backend/node/node_modules/jest" />
|
||||||
|
<working-dir value="$PROJECT_DIR$/tdrive/backend/node" />
|
||||||
|
<envs>
|
||||||
|
<env name="DB_DRIVER" value="mongodb" />
|
||||||
|
<env name="DB_MONGO_URI" value="mongodb://localhost:27017" />
|
||||||
|
<env name="PUBSUB_TYPE" value="local" />
|
||||||
|
<env name="SEARCH_DRIVER" value="mongodb" />
|
||||||
|
<env name="STORAGE_LOCAL_PATH" value="$USER_HOME$/dev/linagora/TDrive/tdrive/docker-data" />
|
||||||
|
</envs>
|
||||||
|
<scope-kind value="TEST" />
|
||||||
|
<test-file value="$PROJECT_DIR$/tdrive/backend/node/test/e2e/documents/documents.spec.ts" />
|
||||||
|
<test-names>
|
||||||
|
<test-name value="the Drive feature" />
|
||||||
|
<test-name value="did search a file shared by another user" />
|
||||||
|
</test-names>
|
||||||
|
<method v="2" />
|
||||||
|
</configuration>
|
||||||
|
</component>
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
<component name="ProjectRunConfigurationManager">
|
||||||
|
<configuration default="false" name="the Drive feature" type="JavaScriptTestRunnerJest" nameIsGenerated="true">
|
||||||
|
<node-interpreter value="project" />
|
||||||
|
<jest-package value="$PROJECT_DIR$/tdrive/backend/node/node_modules/jest" />
|
||||||
|
<working-dir value="$PROJECT_DIR$/tdrive/backend/node" />
|
||||||
|
<envs>
|
||||||
|
<env name="DB_DRIVER" value="mongodb" />
|
||||||
|
<env name="DB_MONGO_URI" value="mongodb://localhost:27017" />
|
||||||
|
<env name="PUBSUB_TYPE" value="local" />
|
||||||
|
<env name="SEARCH_DRIVER" value="mongodb" />
|
||||||
|
<env name="STORAGE_LOCAL_PATH" value="$USER_HOME$/dev/linagora/TDrive/tdrive/docker-data" />
|
||||||
|
</envs>
|
||||||
|
<scope-kind value="SUITE" />
|
||||||
|
<test-file value="$PROJECT_DIR$/tdrive/backend/node/test/e2e/documents/documents.spec.ts" />
|
||||||
|
<test-names>
|
||||||
|
<test-name value="the Drive feature" />
|
||||||
|
</test-names>
|
||||||
|
<method v="2" />
|
||||||
|
</configuration>
|
||||||
|
</component>
|
||||||
@@ -19,5 +19,11 @@
|
|||||||
"amqp": {
|
"amqp": {
|
||||||
"urls": ["amqp://guest:guest@rabbitmq:5672"]
|
"urls": ["amqp://guest:guest@rabbitmq:5672"]
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"search": {
|
||||||
|
"type": "elasticsearch",
|
||||||
|
"elasticsearch": {
|
||||||
|
"endpoint": "https://localhost:9200"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -149,6 +149,10 @@ export class CrudException extends Error {
|
|||||||
static badGateway(details: string): CrudException {
|
static badGateway(details: string): CrudException {
|
||||||
return new CrudException(details, 502);
|
return new CrudException(details, 502);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static throwMe(cause: Error, newOne: CrudException): void {
|
||||||
|
throw cause instanceof CrudException ? cause : newOne;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Paginable {
|
export interface Paginable {
|
||||||
@@ -202,7 +206,7 @@ export interface CRUDService<Entity, PrimaryKey, Context extends ExecutionContex
|
|||||||
* Save a resource.
|
* Save a resource.
|
||||||
* If the resource exists, it is updated, if it does not exists, it is created.
|
* If the resource exists, it is updated, if it does not exists, it is created.
|
||||||
*
|
*
|
||||||
* @param itemOrItems
|
* @param item
|
||||||
* @param options
|
* @param options
|
||||||
* @param context
|
* @param context
|
||||||
*/
|
*/
|
||||||
@@ -223,6 +227,8 @@ export interface CRUDService<Entity, PrimaryKey, Context extends ExecutionContex
|
|||||||
/**
|
/**
|
||||||
* List a resource
|
* List a resource
|
||||||
*
|
*
|
||||||
|
* @param pagination
|
||||||
|
* @param options
|
||||||
* @param context
|
* @param context
|
||||||
*/
|
*/
|
||||||
list<ListOptions>(
|
list<ListOptions>(
|
||||||
|
|||||||
+1
-1
@@ -49,7 +49,7 @@ export function buildComparison(where: any, options: FindOptions = {}): string[]
|
|||||||
if (operator === "$gt" || operator === "$gte" || operator === "$lt" || operator === "$lte") {
|
if (operator === "$gt" || operator === "$gte" || operator === "$lt" || operator === "$lte") {
|
||||||
(options[operator] || []).forEach(element => {
|
(options[operator] || []).forEach(element => {
|
||||||
if (!where[element[0]]) where[element[0]] = {};
|
if (!where[element[0]]) where[element[0]] = {};
|
||||||
where[element[0]][operator] = element[1];
|
where[element[0]][operator] = parseInt(element[1]);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
+2
-2
@@ -16,9 +16,9 @@ export type FindFilter = { [key: string]: any };
|
|||||||
export type RemoveFilter = { [key: string]: any };
|
export type RemoveFilter = { [key: string]: any };
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
type comparisonType = [string, any];
|
export type comparisonType = [string, any];
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
type inType = [string, Array<any>];
|
export type inType = [string, Array<any>];
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
type likeType = [string, any];
|
type likeType = [string, any];
|
||||||
|
|
||||||
|
|||||||
@@ -42,6 +42,9 @@ export default class ElasticSearch extends SearchAdapter implements SearchAdapte
|
|||||||
try {
|
try {
|
||||||
this.client = new Client({
|
this.client = new Client({
|
||||||
node: this.configuration.endpoint,
|
node: this.configuration.endpoint,
|
||||||
|
ssl: {
|
||||||
|
rejectUnauthorized: false,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.error(`Unable to connect to ElasticSearch at ${this.configuration.endpoint}`);
|
logger.error(`Unable to connect to ElasticSearch at ${this.configuration.endpoint}`);
|
||||||
|
|||||||
+39
-1
@@ -3,6 +3,7 @@ import { TransportRequestOptions } from "@elastic/elasticsearch/lib/Transport";
|
|||||||
import { logger } from "../../../../../../core/platform/framework/logger";
|
import { logger } from "../../../../../../core/platform/framework/logger";
|
||||||
import { EntityTarget, FindFilter, FindOptions, getEntityDefinition } from "../../api";
|
import { EntityTarget, FindFilter, FindOptions, getEntityDefinition } from "../../api";
|
||||||
import { asciiFold } from "../utils";
|
import { asciiFold } from "../utils";
|
||||||
|
import { comparisonType } from "src/core/platform/services/database/services/orm/repository/repository";
|
||||||
|
|
||||||
export function buildSearchQuery<Entity>(
|
export function buildSearchQuery<Entity>(
|
||||||
entityType: EntityTarget<Entity>,
|
entityType: EntityTarget<Entity>,
|
||||||
@@ -45,7 +46,44 @@ export function buildSearchQuery<Entity>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO implement $gte, $lt, etc
|
function buildRangeQuery(
|
||||||
|
lteOperations: comparisonType[],
|
||||||
|
key: "gte" | "lte" | "lt" | "gt",
|
||||||
|
): void {
|
||||||
|
for (const lteOperation of lteOperations) {
|
||||||
|
if (lteOperation?.length == 2 && lteOperation[0]) {
|
||||||
|
const field_name = lteOperation[0];
|
||||||
|
esBody.query.bool.must.push({
|
||||||
|
range: {
|
||||||
|
[field_name]: {
|
||||||
|
[key]: lteOperation[1] ? parseInt(lteOperation[1]) : 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
logger.warn(`Not enough data to include to the query: ${lteOperation}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.$lte?.length) {
|
||||||
|
esBody.query.bool.must = esBody.query.bool.must || [];
|
||||||
|
buildRangeQuery(options.$lte, "lte");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.$gte?.length) {
|
||||||
|
esBody.query.bool.must = esBody.query.bool.must || [];
|
||||||
|
buildRangeQuery(options.$gte, "gte");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.$lt?.length) {
|
||||||
|
esBody.query.bool.must = esBody.query.bool.must || [];
|
||||||
|
buildRangeQuery(options.$lt, "lt");
|
||||||
|
}
|
||||||
|
if (options.$gt?.length) {
|
||||||
|
esBody.query.bool.must = esBody.query.bool.must || [];
|
||||||
|
buildRangeQuery(options.$gt, "gt");
|
||||||
|
}
|
||||||
|
|
||||||
if (options.$text) {
|
if (options.$text) {
|
||||||
esBody.query.bool.minimum_should_match = 1;
|
esBody.query.bool.minimum_should_match = 1;
|
||||||
|
|||||||
@@ -84,7 +84,9 @@ export default class MongoSearch extends SearchAdapter implements SearchAdapterI
|
|||||||
|
|
||||||
//Create one index for each type of indexes ["text"]
|
//Create one index for each type of indexes ["text"]
|
||||||
Object.keys(indexedFields).forEach(k => {
|
Object.keys(indexedFields).forEach(k => {
|
||||||
collection.createIndex(indexedFields[k], { default_language: "none" });
|
collection.createIndex(indexedFields[k], {
|
||||||
|
default_language: "none",
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -58,6 +58,10 @@ export function buildSearchQuery<Entity>(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (options.$sort) {
|
||||||
|
sort = options.$sort;
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
project,
|
project,
|
||||||
query,
|
query,
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ type TextType = {
|
|||||||
$diacriticSensitive?: boolean; //Default false
|
$diacriticSensitive?: boolean; //Default false
|
||||||
};
|
};
|
||||||
|
|
||||||
type SortType = {
|
export type SortType = {
|
||||||
[key: string]: "asc" | "desc";
|
[key: string]: "asc" | "desc";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -8,43 +8,36 @@ export default {
|
|||||||
creator: entity.creator,
|
creator: entity.creator,
|
||||||
added: entity.added,
|
added: entity.added,
|
||||||
name: entity.name,
|
name: entity.name,
|
||||||
|
//especially for ES because it doesn't allow to sort by not a keyword fields
|
||||||
|
name_keyword: entity.name,
|
||||||
company_id: entity.company_id,
|
company_id: entity.company_id,
|
||||||
access_users: ["user_1234", "user_454"],
|
access_entities: entity.access_info?.entities?.filter(e => e.level != "none").map(e => e.id),
|
||||||
access_users_x_initiator: ["user_1234_x_user_4343", "user_1234_x_user_4343"],
|
last_modified: entity.last_modified,
|
||||||
|
mime_type: entity.last_version_cache?.file_metadata?.mime,
|
||||||
access_entities: [
|
|
||||||
{
|
|
||||||
type: "user",
|
|
||||||
level: "read",
|
|
||||||
target: "user_1234",
|
|
||||||
source: "user_5678",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: "user",
|
|
||||||
level: "read",
|
|
||||||
target: "user_abcd",
|
|
||||||
source: "user_5678",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}),
|
}),
|
||||||
mongoMapping: {
|
mongoMapping: {
|
||||||
text: {
|
text: {
|
||||||
content_keywords: "text",
|
content_keywords: "text",
|
||||||
tags: "text",
|
tags: "text",
|
||||||
creator: "text",
|
creator: "text",
|
||||||
added: "text",
|
|
||||||
name: "text",
|
name: "text",
|
||||||
|
name_keyword: "text",
|
||||||
company_id: "text",
|
company_id: "text",
|
||||||
|
mime_type: "text",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
esMapping: {
|
esMapping: {
|
||||||
properties: {
|
properties: {
|
||||||
name: { type: "text", index_prefixes: { min_chars: 1 } },
|
name: { type: "text", index_prefixes: { min_chars: 1 } },
|
||||||
|
name_keyword: { type: "keyword" },
|
||||||
content_keywords: { type: "text", index_prefixes: { min_chars: 1 } },
|
content_keywords: { type: "text", index_prefixes: { min_chars: 1 } },
|
||||||
tags: { type: "keyword" },
|
tags: { type: "keyword" },
|
||||||
creator: { type: "keyword" },
|
creator: { type: "keyword" },
|
||||||
added: { type: "keyword" },
|
added: { type: "unsigned_long" },
|
||||||
company_id: { type: "keyword" },
|
company_id: { type: "keyword" },
|
||||||
|
access_entities: { type: "keyword" },
|
||||||
|
mime_type: { type: "keyword" },
|
||||||
|
last_modified: { type: "unsigned_long" },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -48,13 +48,13 @@ export class DriveFile {
|
|||||||
@Column("tags", "encoded_json")
|
@Column("tags", "encoded_json")
|
||||||
tags: string[];
|
tags: string[];
|
||||||
|
|
||||||
@Type(() => String)
|
@Type(() => Number)
|
||||||
@Column("added", "string")
|
@Column("added", "number")
|
||||||
added: string;
|
added: number;
|
||||||
|
|
||||||
@Type(() => String)
|
@Type(() => Number)
|
||||||
@Column("last_modified", "string")
|
@Column("last_modified", "number")
|
||||||
last_modified: string;
|
last_modified: number;
|
||||||
|
|
||||||
@Column("access_info", "encoded_json")
|
@Column("access_info", "encoded_json")
|
||||||
access_info: AccessInformation;
|
access_info: AccessInformation;
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
import SearchRepository from "../../../core/platform/services/search/repository";
|
import SearchRepository from "../../../core/platform/services/search/repository";
|
||||||
import { getLogger, logger, TdriveLogger } from "../../../core/platform/framework";
|
import { getLogger, logger, TdriveLogger } from "../../../core/platform/framework";
|
||||||
import { CrudException, ListResult } from "../../../core/platform/framework/api/crud-service";
|
import { CrudException, ListResult } from "../../../core/platform/framework/api/crud-service";
|
||||||
import Repository from "../../../core/platform/services/database/services/orm/repository/repository";
|
import Repository, {
|
||||||
|
inType,
|
||||||
|
comparisonType,
|
||||||
|
} from "../../../core/platform/services/database/services/orm/repository/repository";
|
||||||
import { PublicFile } from "../../../services/files/entities/file";
|
import { PublicFile } from "../../../services/files/entities/file";
|
||||||
import globalResolver from "../../../services/global-resolver";
|
import globalResolver from "../../../services/global-resolver";
|
||||||
import { hasCompanyAdminLevel } from "../../../utils/company";
|
import { hasCompanyAdminLevel } from "../../../utils/company";
|
||||||
@@ -291,9 +294,9 @@ export class DocumentsService {
|
|||||||
await this.repository.save(driveItem);
|
await this.repository.save(driveItem);
|
||||||
await updateItemSize(driveItem.parent_id, this.repository, context);
|
await updateItemSize(driveItem.parent_id, this.repository, context);
|
||||||
|
|
||||||
this.notifyWebsocket(driveItem.parent_id, context);
|
await this.notifyWebsocket(driveItem.parent_id, context);
|
||||||
|
|
||||||
globalResolver.platformServices.messageQueue.publish<DocumentsMessageQueueRequest>(
|
await globalResolver.platformServices.messageQueue.publish<DocumentsMessageQueueRequest>(
|
||||||
"services:documents:process",
|
"services:documents:process",
|
||||||
{
|
{
|
||||||
data: {
|
data: {
|
||||||
@@ -307,7 +310,7 @@ export class DocumentsService {
|
|||||||
return driveItem;
|
return driveItem;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.logger.error("Failed to create drive item", error);
|
this.logger.error("Failed to create drive item", error);
|
||||||
throw new CrudException("Failed to create item", 500);
|
CrudException.throwMe(error, new CrudException("Failed to create item", 500));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -768,12 +771,39 @@ export class DocumentsService {
|
|||||||
pagination: {
|
pagination: {
|
||||||
limitStr: "100",
|
limitStr: "100",
|
||||||
},
|
},
|
||||||
...(options.company_id ? { $in: [["company_id", [options.company_id]]] } : {}),
|
$in: [
|
||||||
...(options.creator ? { $in: [["creator", [options.creator]]] } : {}),
|
["access_entities", [context.user.id, context.company.id]],
|
||||||
...(options.added ? { $in: [["added", [options.added]]] } : {}),
|
...(options.company_id ? [["company_id", [options.company_id]] as inType] : []),
|
||||||
$text: {
|
...(options.creator ? [["creator", [options.creator]] as inType] : []),
|
||||||
$search: options.search,
|
...(options.mime_type
|
||||||
},
|
? [
|
||||||
|
[
|
||||||
|
"mime_type",
|
||||||
|
Array.isArray(options.mime_type) ? options.mime_type : [options.mime_type],
|
||||||
|
] as inType,
|
||||||
|
]
|
||||||
|
: []),
|
||||||
|
],
|
||||||
|
$lte: [
|
||||||
|
...(options.last_modified_lt
|
||||||
|
? [["last_modified", options.last_modified_lt] as comparisonType]
|
||||||
|
: []),
|
||||||
|
...(options.added_lt ? [["added", options.added_lt] as comparisonType] : []),
|
||||||
|
],
|
||||||
|
$gte: [
|
||||||
|
...(options.last_modified_gt
|
||||||
|
? [["last_modified", options.last_modified_gt] as comparisonType]
|
||||||
|
: []),
|
||||||
|
...(options.added_gt ? [["added", options.added_gt] as comparisonType] : []),
|
||||||
|
],
|
||||||
|
...(options.search
|
||||||
|
? {
|
||||||
|
$text: {
|
||||||
|
$search: options.search,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
: {}),
|
||||||
|
...(options.sort ? { $sort: options.sort } : {}),
|
||||||
},
|
},
|
||||||
context,
|
context,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { ExecutionContext } from "../../core/platform/framework/api/crud-service";
|
import { ExecutionContext } from "../../core/platform/framework/api/crud-service";
|
||||||
import { DriveFile } from "./entities/drive-file";
|
import { DriveFile } from "./entities/drive-file";
|
||||||
import { FileVersion } from "./entities/file-version";
|
import { FileVersion } from "./entities/file-version";
|
||||||
|
import { SortType } from "src/core/platform/services/search/api";
|
||||||
|
|
||||||
export interface CompanyExecutionContext extends ExecutionContext {
|
export interface CompanyExecutionContext extends ExecutionContext {
|
||||||
company: { id: string };
|
company: { id: string };
|
||||||
@@ -40,14 +41,24 @@ export type SearchDocumentsOptions = {
|
|||||||
search?: string;
|
search?: string;
|
||||||
company_id?: string;
|
company_id?: string;
|
||||||
creator?: string;
|
creator?: string;
|
||||||
added?: string;
|
added_gt?: number;
|
||||||
|
added_lt?: number;
|
||||||
|
mime_type?: Array<string>;
|
||||||
|
last_modified_gt?: number;
|
||||||
|
last_modified_lt?: number;
|
||||||
|
sort?: SortType;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type SearchDocumentsBody = {
|
export type SearchDocumentsBody = {
|
||||||
search?: string;
|
search?: string;
|
||||||
company_id?: string;
|
company_id?: string;
|
||||||
creator?: string;
|
creator?: string;
|
||||||
added?: string;
|
added_gt?: number;
|
||||||
|
added_lt?: number;
|
||||||
|
mime_type?: [string];
|
||||||
|
last_modified_gt?: number;
|
||||||
|
last_modified_lt?: number;
|
||||||
|
sort?: SortType;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type DocumentsMessageQueueRequest = {
|
export type DocumentsMessageQueueRequest = {
|
||||||
|
|||||||
@@ -47,11 +47,11 @@ export const getDefaultDriveItem = (
|
|||||||
): DriveFile => {
|
): DriveFile => {
|
||||||
const defaultDriveItem = merge<DriveFile, Partial<DriveFile>>(new DriveFile(), {
|
const defaultDriveItem = merge<DriveFile, Partial<DriveFile>>(new DriveFile(), {
|
||||||
company_id: context.company.id,
|
company_id: context.company.id,
|
||||||
added: item.added || new Date().getTime().toString(),
|
added: item.added || new Date().getTime(),
|
||||||
creator: item.creator || context.user?.id,
|
creator: item.creator || context.user?.id,
|
||||||
is_directory: item.is_directory || false,
|
is_directory: item.is_directory || false,
|
||||||
is_in_trash: item.is_in_trash || false,
|
is_in_trash: item.is_in_trash || false,
|
||||||
last_modified: new Date().getTime().toString(),
|
last_modified: new Date().getTime(),
|
||||||
parent_id: item.parent_id || "root",
|
parent_id: item.parent_id || "root",
|
||||||
content_keywords: item.content_keywords || "",
|
content_keywords: item.content_keywords || "",
|
||||||
description: item.description || "",
|
description: item.description || "",
|
||||||
@@ -422,7 +422,9 @@ export const getFileMetadata = async (
|
|||||||
* Finds a suitable name for an item based on items inside the same folder.
|
* Finds a suitable name for an item based on items inside the same folder.
|
||||||
*
|
*
|
||||||
* @param {string} parent_id - the parent id.
|
* @param {string} parent_id - the parent id.
|
||||||
|
* @param id
|
||||||
* @param {string} name - the item name.
|
* @param {string} name - the item name.
|
||||||
|
* @param is_directory
|
||||||
* @param {Repository<DriveFile>} repository - the drive repository.
|
* @param {Repository<DriveFile>} repository - the drive repository.
|
||||||
* @param {CompanyExecutionContext} context - the execution context.
|
* @param {CompanyExecutionContext} context - the execution context.
|
||||||
* @returns {Promise<string>} - the drive item name.
|
* @returns {Promise<string>} - the drive item name.
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ export class DocumentsController {
|
|||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error("Failed to create Drive item", error);
|
logger.error("Failed to create Drive item", error);
|
||||||
throw new CrudException("Failed to create Drive item", 500);
|
CrudException.throwMe(error, new CrudException("Failed to create Drive item", 500));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -73,6 +73,7 @@ export class DocumentsController {
|
|||||||
* Deletes a DriveFile item or empty the trash or delete root folder contents
|
* Deletes a DriveFile item or empty the trash or delete root folder contents
|
||||||
*
|
*
|
||||||
* @param {FastifyRequest} request
|
* @param {FastifyRequest} request
|
||||||
|
* @param {FastifyReply} reply
|
||||||
* @returns {Promise<void>}
|
* @returns {Promise<void>}
|
||||||
*/
|
*/
|
||||||
delete = async (
|
delete = async (
|
||||||
@@ -227,7 +228,7 @@ export class DocumentsController {
|
|||||||
* If the item is a folder, a zip will be automatically generated.
|
* If the item is a folder, a zip will be automatically generated.
|
||||||
*
|
*
|
||||||
* @param {FastifyRequest} request
|
* @param {FastifyRequest} request
|
||||||
* @param {FastifyReply} reply
|
* @param {FastifyReply} response
|
||||||
*/
|
*/
|
||||||
download = async (
|
download = async (
|
||||||
request: FastifyRequest<{
|
request: FastifyRequest<{
|
||||||
@@ -342,26 +343,27 @@ export class DocumentsController {
|
|||||||
): Promise<ListResult<DriveFile>> => {
|
): Promise<ListResult<DriveFile>> => {
|
||||||
try {
|
try {
|
||||||
const context = getDriveExecutionContext(request);
|
const context = getDriveExecutionContext(request);
|
||||||
const { search = "", added = "", company_id = "", creator = "" } = request.body;
|
|
||||||
|
|
||||||
const options: SearchDocumentsOptions = {
|
const options: SearchDocumentsOptions = {
|
||||||
company_id: company_id || context.company.id,
|
...request.body,
|
||||||
...(search ? { search } : {}),
|
company_id: request.body.company_id || context.company.id,
|
||||||
...(added ? { added } : {}),
|
|
||||||
...(creator ? { creator } : {}),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!Object.keys(options).length) {
|
if (!Object.keys(options).length) {
|
||||||
throw Error("Search options are empty");
|
this.throw500Search();
|
||||||
}
|
}
|
||||||
|
|
||||||
return await globalResolver.services.documents.documents.search(options, context);
|
return await globalResolver.services.documents.documents.search(options, context);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error("error while searching for document", error);
|
logger.error("error while searching for document", error);
|
||||||
throw new CrudException("Failed to search for documents", 500);
|
this.throw500Search();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private throw500Search() {
|
||||||
|
throw new CrudException("Failed to search for documents", 500);
|
||||||
|
}
|
||||||
|
|
||||||
getTab = async (
|
getTab = async (
|
||||||
request: FastifyRequest<{
|
request: FastifyRequest<{
|
||||||
Params: { tab_id: string; company_id: string };
|
Params: { tab_id: string; company_id: string };
|
||||||
|
|||||||
@@ -187,6 +187,9 @@ export class FileServiceImpl {
|
|||||||
},
|
},
|
||||||
): Promise<File> {
|
): Promise<File> {
|
||||||
let entity = await this.repository.findOne(pk, {}, context);
|
let entity = await this.repository.findOne(pk, {}, context);
|
||||||
|
if (entity == null) {
|
||||||
|
throw new CrudException(`File not found ${pk.company_id}|${pk.id}`, 404);
|
||||||
|
}
|
||||||
|
|
||||||
if (options?.waitForThumbnail) {
|
if (options?.waitForThumbnail) {
|
||||||
for (let i = 1; i < 100; i++) {
|
for (let i = 1; i < 100; i++) {
|
||||||
|
|||||||
@@ -103,7 +103,9 @@ export interface ResourceEventsPayload {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface PaginationQueryParameters {
|
export interface PaginationQueryParameters {
|
||||||
|
// It is offset for MongoDB and pointer to the next page for ScyllaDB
|
||||||
page_token?: string;
|
page_token?: string;
|
||||||
|
// Page size
|
||||||
limit?: string;
|
limit?: string;
|
||||||
websockets?: boolean;
|
websockets?: boolean;
|
||||||
}
|
}
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 34 KiB |
|
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 3.7 KiB |
@@ -0,0 +1,192 @@
|
|||||||
|
// @ts-ignore
|
||||||
|
import fs from "fs";
|
||||||
|
import {ResourceUpdateResponse, Workspace} from "../../../src/utils/types";
|
||||||
|
import {File} from "../../../src/services/files/entities/file";
|
||||||
|
import {deserialize} from "class-transformer";
|
||||||
|
import formAutoContent from "form-auto-content";
|
||||||
|
import {TestPlatform, User} from "../setup";
|
||||||
|
import {v1 as uuidv1} from "uuid";
|
||||||
|
import {TestDbService} from "../utils.prepare.db";
|
||||||
|
import {DriveFile} from "../../../src/services/documents/entities/drive-file";
|
||||||
|
import {FileVersion} from "../../../src/services/documents/entities/file-version";
|
||||||
|
import {SearchResultMockClass} from "./entities/mock_entities";
|
||||||
|
import {logger} from "../../../src/core/platform/framework";
|
||||||
|
|
||||||
|
export default class TestHelpers {
|
||||||
|
|
||||||
|
private static readonly DOC_URL = "/internal/services/documents/v1";
|
||||||
|
|
||||||
|
static readonly ALL_FILES = [
|
||||||
|
"sample.png",
|
||||||
|
"sample.gif",
|
||||||
|
"sample.pdf",
|
||||||
|
"sample.doc",
|
||||||
|
"sample.zip",
|
||||||
|
"sample.mp4",
|
||||||
|
]
|
||||||
|
|
||||||
|
platform: TestPlatform;
|
||||||
|
dbService: TestDbService;
|
||||||
|
user: User;
|
||||||
|
workspace: Workspace;
|
||||||
|
jwt: string;
|
||||||
|
|
||||||
|
private constructor(
|
||||||
|
platform: TestPlatform,
|
||||||
|
) {
|
||||||
|
this.platform = platform
|
||||||
|
}
|
||||||
|
|
||||||
|
private async init(newUser: boolean) {
|
||||||
|
this.dbService = await TestDbService.getInstance(this.platform, true);
|
||||||
|
if (newUser) {
|
||||||
|
this.workspace = this.platform.workspace;
|
||||||
|
const workspacePK = {id: this.workspace.workspace_id, company_id: this.workspace.company_id};
|
||||||
|
this.user = await this.dbService.createUser([workspacePK], {}, uuidv1());
|
||||||
|
} else {
|
||||||
|
this.user = this.platform.currentUser;
|
||||||
|
this.workspace = this.platform.workspace;
|
||||||
|
}
|
||||||
|
this.jwt = this.getJWTTokenForUser(this.user.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async getInstance(platform: TestPlatform, newUser = false): Promise<TestHelpers> {
|
||||||
|
const helpers = new TestHelpers(platform);
|
||||||
|
await helpers.init(newUser)
|
||||||
|
return helpers;
|
||||||
|
}
|
||||||
|
|
||||||
|
async uploadFiles() {
|
||||||
|
return Promise.all(TestHelpers.ALL_FILES.map(f => this.uploadFile(f)));
|
||||||
|
}
|
||||||
|
|
||||||
|
async uploadRandomFile() {
|
||||||
|
return await this.uploadFile(TestHelpers.ALL_FILES[Math.floor((Math.random()*TestHelpers.ALL_FILES.length))])
|
||||||
|
}
|
||||||
|
|
||||||
|
async uploadFile(filename: string) {
|
||||||
|
logger.info(`Upload ${filename} for the user: ${this.user.id}`);
|
||||||
|
const fullPath = `${__dirname}/assets/${filename}`
|
||||||
|
const url = "/internal/services/files/v1";
|
||||||
|
const form = formAutoContent({file: fs.createReadStream(fullPath)});
|
||||||
|
form.headers["authorization"] = `Bearer ${this.jwt}`;
|
||||||
|
|
||||||
|
const filesUploadRaw = await this.platform.app.inject({
|
||||||
|
method: "POST",
|
||||||
|
url: `${url}/companies/${this.platform.workspace.company_id}/files?thumbnail_sync=1`,
|
||||||
|
...form,
|
||||||
|
});
|
||||||
|
|
||||||
|
const filesUpload: ResourceUpdateResponse<File> = deserialize(
|
||||||
|
ResourceUpdateResponse,
|
||||||
|
filesUploadRaw.body,
|
||||||
|
);
|
||||||
|
return filesUpload.resource;
|
||||||
|
}
|
||||||
|
|
||||||
|
private getJWTTokenForUser(userId: string): string {
|
||||||
|
const payload = {
|
||||||
|
sub: userId,
|
||||||
|
role: "",
|
||||||
|
}
|
||||||
|
return this.platform.authService.sign(payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
async uploadFileAndCreateDocument(
|
||||||
|
filename: string
|
||||||
|
) {
|
||||||
|
return this.uploadFile(filename).then(f => this.createDocumentFromFile(f));
|
||||||
|
};
|
||||||
|
|
||||||
|
async uploadRandomFileAndCreateDocument() {
|
||||||
|
return this.uploadRandomFile().then(f => this.createDocumentFromFile(f));
|
||||||
|
};
|
||||||
|
|
||||||
|
async uploadAllFilesAndCreateDocuments() {
|
||||||
|
return await Promise.all(TestHelpers.ALL_FILES.map(f => this.uploadFileAndCreateDocument(f)))
|
||||||
|
};
|
||||||
|
|
||||||
|
async uploadAllFilesOneByOne() {
|
||||||
|
const files: Array<DriveFile> = [];
|
||||||
|
for (const idx in TestHelpers.ALL_FILES) {
|
||||||
|
const f = await this.uploadFile(TestHelpers.ALL_FILES[idx]);
|
||||||
|
const doc = await this.createDocumentFromFile(f);
|
||||||
|
files.push(doc);
|
||||||
|
}
|
||||||
|
return files;
|
||||||
|
};
|
||||||
|
|
||||||
|
async createDocument(
|
||||||
|
platform: TestPlatform,
|
||||||
|
item: Partial<DriveFile>,
|
||||||
|
version: Partial<FileVersion>
|
||||||
|
) {
|
||||||
|
|
||||||
|
return await platform.app.inject({
|
||||||
|
method: "POST",
|
||||||
|
url: `${TestHelpers.DOC_URL}/companies/${platform.workspace.company_id}/item`,
|
||||||
|
headers: {
|
||||||
|
authorization: `Bearer ${this.jwt}`,
|
||||||
|
},
|
||||||
|
payload: {
|
||||||
|
item,
|
||||||
|
version,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
async createDocumentFromFile(
|
||||||
|
file: File
|
||||||
|
) {
|
||||||
|
const item = {
|
||||||
|
name: file.metadata.name,
|
||||||
|
parent_id: "root",
|
||||||
|
company_id: file.company_id,
|
||||||
|
};
|
||||||
|
|
||||||
|
const version = {
|
||||||
|
file_metadata: {
|
||||||
|
name: file.metadata.name,
|
||||||
|
size: file.upload_data?.size,
|
||||||
|
thumbnails: [],
|
||||||
|
external_id: file.id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await this.createDocument(this.platform, item, version);
|
||||||
|
return deserialize<DriveFile>(DriveFile, response.body);
|
||||||
|
};
|
||||||
|
|
||||||
|
async updateDocument(
|
||||||
|
id: string | "root" | "trash",
|
||||||
|
item: Partial<DriveFile>
|
||||||
|
) {
|
||||||
|
return await this.platform.app.inject({
|
||||||
|
method: "POST",
|
||||||
|
url: `${TestHelpers.DOC_URL}/companies/${this.platform.workspace.company_id}/item/${id}`,
|
||||||
|
headers: {
|
||||||
|
authorization: `Bearer ${this.jwt}`,
|
||||||
|
},
|
||||||
|
payload: item,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
async searchDocument (
|
||||||
|
payload: Record<string, any>
|
||||||
|
){
|
||||||
|
const response = await this.platform.app.inject({
|
||||||
|
method: "POST",
|
||||||
|
url: `${TestHelpers.DOC_URL}/companies/${this.platform.workspace.company_id}/search`,
|
||||||
|
headers: {
|
||||||
|
authorization: `Bearer ${this.jwt}`,
|
||||||
|
},
|
||||||
|
payload,
|
||||||
|
});
|
||||||
|
|
||||||
|
return deserialize<SearchResultMockClass>(
|
||||||
|
SearchResultMockClass,
|
||||||
|
response.body)
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
import {DriveFileAccessLevel, publicAccessLevel} from "../../../../src/services/documents/types";
|
||||||
|
|
||||||
|
export type MockAccessInformation = {
|
||||||
|
public?: {
|
||||||
|
token: string;
|
||||||
|
password: string;
|
||||||
|
expiration: number;
|
||||||
|
level: publicAccessLevel;
|
||||||
|
};
|
||||||
|
entities: MockAuthEntity[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export type MockAuthEntity = {
|
||||||
|
type: "user" | "channel" | "company" | "folder";
|
||||||
|
id: string | "parent";
|
||||||
|
level: publicAccessLevel | DriveFileAccessLevel;
|
||||||
|
};
|
||||||
|
|
||||||
|
export class DriveFileMockClass {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
size: number;
|
||||||
|
added: string;
|
||||||
|
parent_id: string;
|
||||||
|
extension: string;
|
||||||
|
description: string;
|
||||||
|
tags: string[];
|
||||||
|
last_modified: string;
|
||||||
|
access_info: MockAccessInformation;
|
||||||
|
creator: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class DriveItemDetailsMockClass {
|
||||||
|
path: string[];
|
||||||
|
item: DriveFileMockClass;
|
||||||
|
children: DriveFileMockClass[];
|
||||||
|
versions: Record<string, unknown>[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export class SearchResultMockClass {
|
||||||
|
entities: DriveFileMockClass[];
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import { describe, beforeEach, afterEach, it, expect, afterAll } from "@jest/globals";
|
import { describe, beforeEach, afterEach, it, expect, afterAll } from "@jest/globals";
|
||||||
import { deserialize } from "class-transformer";
|
import {deserialize} from "class-transformer";
|
||||||
import { File } from "../../../src/services/files/entities/file";
|
import { File } from "../../../src/services/files/entities/file";
|
||||||
import { ResourceUpdateResponse } from "../../../src/utils/types";
|
import { ResourceUpdateResponse } from "../../../src/utils/types";
|
||||||
import { init, TestPlatform } from "../setup";
|
import { init, TestPlatform } from "../setup";
|
||||||
@@ -13,28 +13,13 @@ import {
|
|||||||
e2e_searchDocument,
|
e2e_searchDocument,
|
||||||
e2e_updateDocument,
|
e2e_updateDocument,
|
||||||
} from "./utils";
|
} from "./utils";
|
||||||
|
import TestHelpers from "../common/common_test_helpers";
|
||||||
|
import {DriveFileMockClass, DriveItemDetailsMockClass, SearchResultMockClass} from "../common/entities/mock_entities";
|
||||||
|
|
||||||
describe("the Drive feature", () => {
|
describe("the Drive feature", () => {
|
||||||
let platform: TestPlatform;
|
let platform: TestPlatform;
|
||||||
|
let currentUser: TestHelpers;
|
||||||
class DriveFileMockClass {
|
let dbService: TestDbService;
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
size: number;
|
|
||||||
added: string;
|
|
||||||
parent_id: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
class DriveItemDetailsMockClass {
|
|
||||||
path: string[];
|
|
||||||
item: DriveFileMockClass;
|
|
||||||
children: DriveFileMockClass[];
|
|
||||||
versions: Record<string, unknown>[];
|
|
||||||
}
|
|
||||||
|
|
||||||
class SearchResultMockClass {
|
|
||||||
entities: DriveFileMockClass[];
|
|
||||||
}
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
platform = await init({
|
platform = await init({
|
||||||
@@ -59,6 +44,8 @@ describe("the Drive feature", () => {
|
|||||||
"documents",
|
"documents",
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
currentUser = await TestHelpers.getInstance(platform);
|
||||||
|
dbService = await TestDbService.getInstance(platform, true);
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(async () => {
|
afterEach(async () => {
|
||||||
@@ -170,8 +157,8 @@ describe("the Drive feature", () => {
|
|||||||
done?.();
|
done?.();
|
||||||
});
|
});
|
||||||
|
|
||||||
// TODO: wait for elastic search index
|
|
||||||
it("did search for an item", async done => {
|
it("did search for an item", async done => {
|
||||||
|
jest.setTimeout(10000);
|
||||||
const createItemResult = await createItem();
|
const createItemResult = await createItem();
|
||||||
|
|
||||||
expect(createItemResult.id).toBeDefined();
|
expect(createItemResult.id).toBeDefined();
|
||||||
@@ -237,4 +224,230 @@ describe("the Drive feature", () => {
|
|||||||
|
|
||||||
done?.();
|
done?.();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("did search by mime type", async done => {
|
||||||
|
// given:: all the sample files uploaded and documents for them created
|
||||||
|
await Promise.all((await currentUser.uploadFiles()).map(f => currentUser.createDocumentFromFile(f)))
|
||||||
|
|
||||||
|
const filters = {
|
||||||
|
mime_type: "application/pdf",
|
||||||
|
};
|
||||||
|
|
||||||
|
jest.setTimeout(10000);
|
||||||
|
await new Promise(r => setTimeout(r, 5000));
|
||||||
|
|
||||||
|
|
||||||
|
let documents = await currentUser.searchDocument(filters);
|
||||||
|
expect(documents.entities).toHaveLength(1);
|
||||||
|
|
||||||
|
const actualFile = documents.entities[0];
|
||||||
|
expect(actualFile.name).toEqual("sample.pdf");
|
||||||
|
|
||||||
|
done?.();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("did search by last modified", async done => {
|
||||||
|
jest.setTimeout(10000);
|
||||||
|
const user = await TestHelpers.getInstance(platform, true);
|
||||||
|
// given:: all the sample files uploaded and documents for them created
|
||||||
|
const start = new Date().getTime();
|
||||||
|
await user.uploadAllFilesOneByOne()
|
||||||
|
const end = new Date().getTime();
|
||||||
|
await user.uploadAllFilesOneByOne()
|
||||||
|
//wait for putting docs to elastic and its indexing
|
||||||
|
await new Promise(r => setTimeout(r, 3000));
|
||||||
|
|
||||||
|
//then:: all the files are searchable without filters
|
||||||
|
let documents = await user.searchDocument({});
|
||||||
|
expect(documents.entities).toHaveLength(TestHelpers.ALL_FILES.length * 2);
|
||||||
|
|
||||||
|
//then:: only file uploaded in the [start, end] interval are shown in the search results
|
||||||
|
const filters = {
|
||||||
|
last_modified_gt: start.toString(),
|
||||||
|
last_modified_lt: end.toString()
|
||||||
|
};
|
||||||
|
documents = await user.searchDocument(filters);
|
||||||
|
expect(documents.entities).toHaveLength(TestHelpers.ALL_FILES.length);
|
||||||
|
|
||||||
|
done?.();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("did search a file shared by another user", async done => {
|
||||||
|
jest.setTimeout(30000);
|
||||||
|
//given:
|
||||||
|
const oneUser = await TestHelpers.getInstance(platform, true);
|
||||||
|
const anotherUser = await TestHelpers.getInstance(platform, true);
|
||||||
|
//upload files
|
||||||
|
let files = await oneUser.uploadAllFilesOneByOne()
|
||||||
|
|
||||||
|
await new Promise(r => setTimeout(r, 5000));
|
||||||
|
|
||||||
|
//then:: files are not searchable for user without permissions
|
||||||
|
expect((await anotherUser.searchDocument({})).entities).toHaveLength(0);
|
||||||
|
|
||||||
|
//and searchable for user that have
|
||||||
|
expect((await oneUser.searchDocument({})).entities).toHaveLength(TestHelpers.ALL_FILES.length);
|
||||||
|
|
||||||
|
//give permissions to the file
|
||||||
|
files[0].access_info.entities.push({
|
||||||
|
type: "user",
|
||||||
|
id: anotherUser.user.id,
|
||||||
|
level: "read"
|
||||||
|
})
|
||||||
|
await oneUser.updateDocument(files[0].id, files[0]);
|
||||||
|
await new Promise(r => setTimeout(r, 3000));
|
||||||
|
|
||||||
|
//then file become searchable
|
||||||
|
expect((await anotherUser.searchDocument({})).entities).toHaveLength(1);
|
||||||
|
|
||||||
|
done?.();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("did search a file by file owner", async done => {
|
||||||
|
jest.setTimeout(30000);
|
||||||
|
//given:
|
||||||
|
const oneUser = await TestHelpers.getInstance(platform, true);
|
||||||
|
const anotherUser = await TestHelpers.getInstance(platform, true);
|
||||||
|
//upload files
|
||||||
|
let files = await oneUser.uploadAllFilesOneByOne()
|
||||||
|
await anotherUser.uploadAllFilesOneByOne()
|
||||||
|
//give permissions for all files to 'another user'
|
||||||
|
await Promise.all(files.map(f => {
|
||||||
|
f.access_info.entities.push({
|
||||||
|
type: "user",
|
||||||
|
id: anotherUser.user.id,
|
||||||
|
level: "read"
|
||||||
|
})
|
||||||
|
return oneUser.updateDocument(f.id, f);
|
||||||
|
}));
|
||||||
|
|
||||||
|
await new Promise(r => setTimeout(r, 5000));
|
||||||
|
|
||||||
|
//then:: all files are searchable for 'another user'
|
||||||
|
expect((await anotherUser.searchDocument({})).entities).toHaveLength(TestHelpers.ALL_FILES.length * 2);
|
||||||
|
|
||||||
|
//and searchable for user that have
|
||||||
|
expect((await oneUser.searchDocument({
|
||||||
|
creator: oneUser.user.id,
|
||||||
|
})).entities).toHaveLength(TestHelpers.ALL_FILES.length);
|
||||||
|
|
||||||
|
done?.();
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
it("did search by 'added' date", async done => {
|
||||||
|
jest.setTimeout(10000);
|
||||||
|
const user = await TestHelpers.getInstance(platform, true);
|
||||||
|
// given:: all the sample files uploaded and documents for them created
|
||||||
|
await user.uploadRandomFileAndCreateDocument();
|
||||||
|
const start = new Date().getTime();
|
||||||
|
await user.uploadAllFilesAndCreateDocuments()
|
||||||
|
const end = new Date().getTime();
|
||||||
|
await user.uploadRandomFileAndCreateDocument();
|
||||||
|
//wait for putting docs to elastic and its indexing
|
||||||
|
await new Promise(r => setTimeout(r, 3000));
|
||||||
|
|
||||||
|
//then:: all the files are searchable without filters
|
||||||
|
let documents = await user.searchDocument({});
|
||||||
|
expect(documents.entities).toHaveLength(TestHelpers.ALL_FILES.length + 2);
|
||||||
|
|
||||||
|
//then:: only file uploaded in the [start, end] interval are shown in the search results
|
||||||
|
const filters = {
|
||||||
|
added_gt: start.toString(),
|
||||||
|
added_lt: end.toString()
|
||||||
|
};
|
||||||
|
documents = await user.searchDocument(filters);
|
||||||
|
expect(documents.entities).toHaveLength(TestHelpers.ALL_FILES.length);
|
||||||
|
|
||||||
|
done?.();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("did search order by name", async done => {
|
||||||
|
jest.setTimeout(10000);
|
||||||
|
const user = await TestHelpers.getInstance(platform, true);
|
||||||
|
// given:: all the sample files uploaded and documents for them created
|
||||||
|
await user.uploadAllFilesAndCreateDocuments()
|
||||||
|
//wait for putting docs to elastic and its indexing
|
||||||
|
await new Promise(r => setTimeout(r, 5000));
|
||||||
|
|
||||||
|
//when:: sort files by name is ascending order
|
||||||
|
const options = {
|
||||||
|
sort: {
|
||||||
|
name_keyword: "asc",
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const documents = await user.searchDocument(options);
|
||||||
|
|
||||||
|
//then all the files are sorted properly by name
|
||||||
|
expect(documents.entities.map(e => e.name)).toEqual(TestHelpers.ALL_FILES.sort());
|
||||||
|
done?.();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("did search order by name desc", async done => {
|
||||||
|
jest.setTimeout(10000);
|
||||||
|
const user = await TestHelpers.getInstance(platform, true);
|
||||||
|
// given:: all the sample files uploaded and documents for them created
|
||||||
|
await user.uploadAllFilesOneByOne()
|
||||||
|
//wait for putting docs to elastic and its indexing
|
||||||
|
await new Promise(r => setTimeout(r, 5000));
|
||||||
|
|
||||||
|
//when:: sort files by name is ascending order
|
||||||
|
const options = {
|
||||||
|
sort: {
|
||||||
|
name_keyword: "desc",
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const documents = await user.searchDocument(options);
|
||||||
|
|
||||||
|
//then all the files are sorted properly by name
|
||||||
|
expect(documents.entities.map(e => e.name)).toEqual(TestHelpers.ALL_FILES.sort().reverse());
|
||||||
|
done?.();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("did search order by added date", async done => {
|
||||||
|
jest.setTimeout(10000);
|
||||||
|
const user = await TestHelpers.getInstance(platform, true);
|
||||||
|
// given:: all the sample files uploaded and documents for them created
|
||||||
|
await user.uploadAllFilesOneByOne();
|
||||||
|
//wait for putting docs to elastic and its indexing
|
||||||
|
await new Promise(r => setTimeout(r, 5000));
|
||||||
|
|
||||||
|
//when:: ask to sort files by the 'added' field
|
||||||
|
const options = {
|
||||||
|
sort: {
|
||||||
|
added: "asc",
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const documents = await user.searchDocument(options);
|
||||||
|
|
||||||
|
//then:: files should be sorted properly
|
||||||
|
expect(documents.entities.map(e => e.name)).toEqual(TestHelpers.ALL_FILES);
|
||||||
|
|
||||||
|
done?.();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("did search order by added date desc", async done => {
|
||||||
|
jest.setTimeout(10000);
|
||||||
|
const user = await TestHelpers.getInstance(platform, true);
|
||||||
|
// given:: all the sample files uploaded and documents for them created
|
||||||
|
await user.uploadAllFilesOneByOne();
|
||||||
|
//wait for putting docs to elastic and its indexing
|
||||||
|
await new Promise(r => setTimeout(r, 5000));
|
||||||
|
|
||||||
|
//when:: ask to sort files by the 'added' field desc
|
||||||
|
const options = {
|
||||||
|
sort: {
|
||||||
|
added: "desc",
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const documents = await user.searchDocument(options);
|
||||||
|
|
||||||
|
//then:: files should be sorted properly
|
||||||
|
expect(documents.entities.map(e => e.name)).toEqual(TestHelpers.ALL_FILES.reverse());
|
||||||
|
|
||||||
|
done?.();
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,25 +1,24 @@
|
|||||||
import "reflect-metadata";
|
import "reflect-metadata";
|
||||||
import { afterAll, beforeAll, describe, expect, it } from "@jest/globals";
|
import { afterAll, beforeAll, describe, expect, it } from "@jest/globals";
|
||||||
import { init, TestPlatform } from "../setup";
|
import { init, TestPlatform } from "../setup";
|
||||||
import { ResourceUpdateResponse } from "../../../src/utils/types";
|
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import fs from "fs";
|
import fs from "fs";
|
||||||
import { File } from "../../../src/services/files/entities/file";
|
|
||||||
import { deserialize } from "class-transformer";
|
|
||||||
import formAutoContent from "form-auto-content";
|
|
||||||
import LocalConnectorService from "../../../src/core/platform/services/storage/connectors/local/service";
|
import LocalConnectorService from "../../../src/core/platform/services/storage/connectors/local/service";
|
||||||
|
import TestHelpers from "../common/common_test_helpers";
|
||||||
|
|
||||||
|
|
||||||
describe("The Files feature", () => {
|
describe("The Files feature", () => {
|
||||||
const url = "/internal/services/files/v1";
|
const url = "/internal/services/files/v1";
|
||||||
let platform: TestPlatform;
|
let platform: TestPlatform;
|
||||||
|
let helpers: TestHelpers;
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
platform = await init({
|
platform = await init({
|
||||||
services: ["webserver", "database", "storage", "files", "previews"],
|
services: ["webserver", "database", "storage", "files", "previews"],
|
||||||
});
|
});
|
||||||
await platform.database.getConnector().init();
|
await platform.database.getConnector().init();
|
||||||
|
helpers = await TestHelpers.getInstance(platform)
|
||||||
});
|
});
|
||||||
|
|
||||||
afterAll(async done => {
|
afterAll(async done => {
|
||||||
@@ -28,37 +27,13 @@ describe("The Files feature", () => {
|
|||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
|
|
||||||
async function uploadFile(file: string) {
|
|
||||||
const form = formAutoContent({file: fs.createReadStream(file)});
|
|
||||||
form.headers["authorization"] = `Bearer ${await platform.auth.getJWTToken()}`;
|
|
||||||
|
|
||||||
const filesUploadRaw = await platform.app.inject({
|
|
||||||
method: "POST",
|
|
||||||
url: `${url}/companies/${platform.workspace.company_id}/files?thumbnail_sync=1`,
|
|
||||||
...form,
|
|
||||||
});
|
|
||||||
const filesUpload: ResourceUpdateResponse<File> = deserialize(
|
|
||||||
ResourceUpdateResponse,
|
|
||||||
filesUploadRaw.body,
|
|
||||||
);
|
|
||||||
return filesUpload;
|
|
||||||
}
|
|
||||||
|
|
||||||
describe("On user send files", () => {
|
describe("On user send files", () => {
|
||||||
const files = [
|
|
||||||
"assets/sample.png",
|
|
||||||
"assets/sample.gif",
|
|
||||||
"assets/sample.pdf",
|
|
||||||
"assets/sample.doc",
|
|
||||||
"assets/sample.zip",
|
|
||||||
"assets/sample.mp4",
|
|
||||||
].map(p => `${__dirname}/${p}`);
|
|
||||||
const thumbnails = [1, 1, 2, 5, 0, 1];
|
const thumbnails = [1, 1, 2, 5, 0, 1];
|
||||||
|
|
||||||
it("Download file should return 500 if file doesn't exists", async () => {
|
it("Download file should return 500 if file doesn't exists", async () => {
|
||||||
//given file
|
//given file
|
||||||
const filesUpload = await uploadFile(files[0]);
|
const filesUpload = await helpers.uploadRandomFile();
|
||||||
expect(filesUpload.resource.id).toBeTruthy();
|
expect(filesUpload.id).toBeTruthy();
|
||||||
//clean files directory
|
//clean files directory
|
||||||
expect(platform.storage.getConnector()).toBeInstanceOf(LocalConnectorService)
|
expect(platform.storage.getConnector()).toBeInstanceOf(LocalConnectorService)
|
||||||
const path = (<LocalConnectorService>platform.storage.getConnector()).configuration.path;
|
const path = (<LocalConnectorService>platform.storage.getConnector()).configuration.path;
|
||||||
@@ -66,7 +41,7 @@ describe("The Files feature", () => {
|
|||||||
//when try to download the file
|
//when try to download the file
|
||||||
const fileDownloadResponse = await platform.app.inject({
|
const fileDownloadResponse = await platform.app.inject({
|
||||||
method: "GET",
|
method: "GET",
|
||||||
url: `${url}/companies/${platform.workspace.company_id}/files/${filesUpload.resource.id}/download`,
|
url: `${url}/companies/${platform.workspace.company_id}/files/${filesUpload.id}/download`,
|
||||||
});
|
});
|
||||||
//then file should be not found with 404 error and "File not found message"
|
//then file should be not found with 404 error and "File not found message"
|
||||||
expect(fileDownloadResponse).toBeTruthy();
|
expect(fileDownloadResponse).toBeTruthy();
|
||||||
@@ -76,15 +51,15 @@ describe("The Files feature", () => {
|
|||||||
|
|
||||||
it("Download file should return 200 if file exists", async () => {
|
it("Download file should return 200 if file exists", async () => {
|
||||||
//given file
|
//given file
|
||||||
const filesUpload = await uploadFile(files[0]);
|
const filesUpload = await helpers.uploadRandomFile()
|
||||||
expect(filesUpload.resource.id).toBeTruthy();
|
expect(filesUpload.id).toBeTruthy();
|
||||||
//clean files directory
|
//clean files directory
|
||||||
expect(platform.storage.getConnector()).toBeInstanceOf(LocalConnectorService)
|
expect(platform.storage.getConnector()).toBeInstanceOf(LocalConnectorService)
|
||||||
|
|
||||||
//when try to download the file
|
//when try to download the file
|
||||||
const fileDownloadResponse = await platform.app.inject({
|
const fileDownloadResponse = await platform.app.inject({
|
||||||
method: "GET",
|
method: "GET",
|
||||||
url: `${url}/companies/${platform.workspace.company_id}/files/${filesUpload.resource.id}/download`,
|
url: `${url}/companies/${platform.workspace.company_id}/files/${filesUpload.id}/download`,
|
||||||
});
|
});
|
||||||
//then file should be not found with 404 error and "File not found message"
|
//then file should be not found with 404 error and "File not found message"
|
||||||
expect(fileDownloadResponse).toBeTruthy();
|
expect(fileDownloadResponse).toBeTruthy();
|
||||||
@@ -94,20 +69,20 @@ describe("The Files feature", () => {
|
|||||||
|
|
||||||
|
|
||||||
it.skip("should save file and generate previews", async done => {
|
it.skip("should save file and generate previews", async done => {
|
||||||
for (const i in files) {
|
for (const i in TestHelpers.ALL_FILES) {
|
||||||
const file = files[i];
|
const file = TestHelpers.ALL_FILES[i];
|
||||||
|
|
||||||
const filesUpload = await uploadFile(file);
|
const filesUpload = await helpers.uploadFile(file);
|
||||||
|
|
||||||
expect(filesUpload.resource.id).not.toBeFalsy();
|
expect(filesUpload.id).not.toBeFalsy();
|
||||||
expect(filesUpload.resource.encryption_key).toBeFalsy(); //This must not be disclosed
|
expect(filesUpload.encryption_key).toBeFalsy(); //This must not be disclosed
|
||||||
expect(filesUpload.resource.thumbnails.length).toBe(thumbnails[i]);
|
expect(filesUpload.thumbnails.length).toBe(thumbnails[i]);
|
||||||
|
|
||||||
for (const thumb of filesUpload.resource.thumbnails) {
|
for (const thumb of filesUpload.thumbnails) {
|
||||||
const thumbnails = await platform.app.inject({
|
const thumbnails = await platform.app.inject({
|
||||||
headers: {"authorization": `Bearer ${await platform.auth.getJWTToken()}`},
|
headers: {"authorization": `Bearer ${await platform.auth.getJWTToken()}`},
|
||||||
method: "GET",
|
method: "GET",
|
||||||
url: `${url}/companies/${platform.workspace.company_id}/files/${filesUpload.resource.id}/thumbnails/${thumb.index}`,
|
url: `${url}/companies/${platform.workspace.company_id}/files/${filesUpload.id}/thumbnails/${thumb.index}`,
|
||||||
});
|
});
|
||||||
expect(thumbnails.statusCode).toBe(200);
|
expect(thumbnails.statusCode).toBe(200);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import config from "config";
|
|||||||
import globalResolver from "../../../src/services/global-resolver";
|
import globalResolver from "../../../src/services/global-resolver";
|
||||||
import {FileServiceImpl} from "../../../src/services/files/services";
|
import {FileServiceImpl} from "../../../src/services/files/services";
|
||||||
import StorageAPI from "../../../src/core/platform/services/storage/provider";
|
import StorageAPI from "../../../src/core/platform/services/storage/provider";
|
||||||
|
import {SearchServiceAPI} from "../../../src/core/platform/services/search/api";
|
||||||
|
|
||||||
type TokenPayload = {
|
type TokenPayload = {
|
||||||
sub: string;
|
sub: string;
|
||||||
@@ -23,7 +24,7 @@ type TokenPayload = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
type User = {
|
export type User = {
|
||||||
id: string;
|
id: string;
|
||||||
isWorkspaceModerator?: boolean;
|
isWorkspaceModerator?: boolean;
|
||||||
};
|
};
|
||||||
@@ -42,6 +43,7 @@ export interface TestPlatform {
|
|||||||
getJWTToken(payload?: TokenPayload): Promise<string>;
|
getJWTToken(payload?: TokenPayload): Promise<string>;
|
||||||
};
|
};
|
||||||
tearDown(): Promise<void>;
|
tearDown(): Promise<void>;
|
||||||
|
search: SearchServiceAPI;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TestPlatformConfiguration {
|
export interface TestPlatformConfiguration {
|
||||||
@@ -76,6 +78,7 @@ export async function init(
|
|||||||
const messageQueue = platform.getProvider<MessageQueueServiceAPI>("message-queue");
|
const messageQueue = platform.getProvider<MessageQueueServiceAPI>("message-queue");
|
||||||
const auth = platform.getProvider<AuthServiceAPI>("auth");
|
const auth = platform.getProvider<AuthServiceAPI>("auth");
|
||||||
const storage: StorageAPI = platform.getProvider<StorageAPI>("storage");
|
const storage: StorageAPI = platform.getProvider<StorageAPI>("storage");
|
||||||
|
const search: SearchServiceAPI = platform.getProvider<SearchServiceAPI>("search");
|
||||||
|
|
||||||
testPlatform = {
|
testPlatform = {
|
||||||
platform,
|
platform,
|
||||||
@@ -91,6 +94,7 @@ export async function init(
|
|||||||
getJWTToken,
|
getJWTToken,
|
||||||
},
|
},
|
||||||
tearDown,
|
tearDown,
|
||||||
|
search,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -112,7 +116,7 @@ export async function init(
|
|||||||
payload.sub = testPlatform.currentUser.id;
|
payload.sub = testPlatform.currentUser.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (testPlatform .currentUser.isWorkspaceModerator) {
|
if (testPlatform.currentUser.isWorkspaceModerator) {
|
||||||
payload.org = {};
|
payload.org = {};
|
||||||
payload.org[testPlatform.workspace.company_id] = {
|
payload.org[testPlatform.workspace.company_id] = {
|
||||||
role: "",
|
role: "",
|
||||||
|
|||||||
+6
@@ -6,6 +6,12 @@
|
|||||||
"level": "warn"
|
"level": "warn"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"search": {
|
||||||
|
"type": "elasticsearch",
|
||||||
|
"elasticsearch": {
|
||||||
|
"endpoint": "http://localhost:9200"
|
||||||
|
}
|
||||||
|
},
|
||||||
"websocket": {
|
"websocket": {
|
||||||
"path": "/socket",
|
"path": "/socket",
|
||||||
"adapters": {
|
"adapters": {
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
@company_id = bcfe2f79-8e81-42a3-b551-3a32d49b2b4c
|
|
||||||
@workspace_id = 3328552c-5ccd-4172-b84a-d876d56aa71b
|
|
||||||
@user_id = 3328552c-5ccd-4172-b84a-d876d56aa71c
|
|
||||||
|
|
||||||
@baseURL = http://localhost:3000
|
|
||||||
@badgesURL = {{baseURL}}/internal/services/notifications/v1/badges
|
|
||||||
|
|
||||||
# @name login
|
|
||||||
GET {{baseURL}}/api/auth/login
|
|
||||||
|
|
||||||
@authToken = {{login.response.body.token}}
|
|
||||||
@currentUserId = {{login.response.body.user.id}}
|
|
||||||
|
|
||||||
### List badges with all websockets
|
|
||||||
|
|
||||||
GET {{badgesURL}}/?company_id={{company_id}}&websockets=true&limit=5
|
|
||||||
Content-Type: application/json
|
|
||||||
Authorization: Bearer {{authToken}}
|
|
||||||
|
|
||||||
@@ -1,61 +0,0 @@
|
|||||||
@company_id = bcfe2f79-8e81-42a3-b551-3a32d49b2b4c
|
|
||||||
@workspace_id = 3328552c-5ccd-4172-b84a-d876d56aa71b
|
|
||||||
@user_id = 3328552c-5ccd-4172-b84a-d876d56aa71c
|
|
||||||
|
|
||||||
@baseURL = http://localhost:3000
|
|
||||||
@channelsURL = {{baseURL}}/internal/services/channels/v1/companies/{{company_id}}/workspaces/{{workspace_id}}
|
|
||||||
@directChannelsURL = {{baseURL}}/internal/services/channels/v1/companies/{{company_id}}/workspaces/direct
|
|
||||||
|
|
||||||
# @name login
|
|
||||||
GET {{baseURL}}/api/auth/login
|
|
||||||
|
|
||||||
@authToken = {{login.response.body.token}}
|
|
||||||
@currentUserId = {{login.response.body.user.id}}
|
|
||||||
|
|
||||||
### Create a channel
|
|
||||||
|
|
||||||
# @name createChannel
|
|
||||||
POST {{channelsURL}}/channels
|
|
||||||
Content-Type: application/json
|
|
||||||
Authorization: Bearer {{authToken}}
|
|
||||||
|
|
||||||
{
|
|
||||||
"resource": {
|
|
||||||
"name": "My channel",
|
|
||||||
"icon": "tdrive logo",
|
|
||||||
"description": "This channel allow tdrive's team to chat easily",
|
|
||||||
"channel_group": "tdrive",
|
|
||||||
"visibility": "public",
|
|
||||||
"is_default": true,
|
|
||||||
"archived": false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
### Get a single channel
|
|
||||||
|
|
||||||
@getId = {{createChannel.response.body.resource.id}}
|
|
||||||
|
|
||||||
GET {{channelsURL}}/channels/{{getId}}
|
|
||||||
Authorization: Bearer {{authToken}}
|
|
||||||
|
|
||||||
### Add current user as member to a channel (join channel)
|
|
||||||
|
|
||||||
POST {{channelsURL}}/channels/{{getId}}/members
|
|
||||||
Content-Type: application/json
|
|
||||||
Authorization: Bearer {{authToken}}
|
|
||||||
|
|
||||||
{
|
|
||||||
"resource": {
|
|
||||||
"user_id": "{{currentUserId}}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
### Mark the channel as read/unread
|
|
||||||
|
|
||||||
POST {{channelsURL}}/channels/{{getId}}/read
|
|
||||||
Content-Type: application/json
|
|
||||||
Authorization: Bearer {{authToken}}
|
|
||||||
|
|
||||||
{
|
|
||||||
"value": true
|
|
||||||
}
|
|
||||||
@@ -1,133 +0,0 @@
|
|||||||
@company_id = bcfe2f79-8e81-42a3-b551-3a32d49b2b4c
|
|
||||||
@workspace_id = 3328552c-5ccd-4172-b84a-d876d56aa71b
|
|
||||||
@user_id = 3328552c-5ccd-4172-b84a-d876d56aa71c
|
|
||||||
|
|
||||||
@baseURL = http://localhost:3000
|
|
||||||
@channelsURL = {{baseURL}}/internal/services/channels/v1/companies/{{company_id}}/workspaces/{{workspace_id}}
|
|
||||||
@directChannelsURL = {{baseURL}}/internal/services/channels/v1/companies/{{company_id}}/workspaces/direct
|
|
||||||
|
|
||||||
# @name login
|
|
||||||
GET {{baseURL}}/api/auth/login
|
|
||||||
|
|
||||||
@authToken = {{login.response.body.token}}
|
|
||||||
@currentUserId = {{login.response.body.user.id}}
|
|
||||||
|
|
||||||
### List workspace channels with all websockets
|
|
||||||
|
|
||||||
GET {{channelsURL}}/channels?websockets=true&limit=5
|
|
||||||
Content-Type: application/json
|
|
||||||
Authorization: Bearer {{authToken}}
|
|
||||||
|
|
||||||
### List user channels with all websockets
|
|
||||||
|
|
||||||
@authToken = {{login.response.body.token}}
|
|
||||||
GET {{channelsURL}}/channels?websockets=true&mine=true&limit=5
|
|
||||||
Content-Type: application/json
|
|
||||||
Authorization: Bearer {{authToken}}
|
|
||||||
|
|
||||||
### Create a channel
|
|
||||||
|
|
||||||
# @name createChannel
|
|
||||||
POST {{channelsURL}}/channels
|
|
||||||
Content-Type: application/json
|
|
||||||
Authorization: Bearer {{authToken}}
|
|
||||||
|
|
||||||
{
|
|
||||||
"resource": {
|
|
||||||
"name": "My channel",
|
|
||||||
"icon": "tdrive logo",
|
|
||||||
"description": "This channel allow tdrive's team to chat easily",
|
|
||||||
"channel_group": "tdrive",
|
|
||||||
"visibility": "public",
|
|
||||||
"is_default": true,
|
|
||||||
"archived": false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
### Get a single channel
|
|
||||||
|
|
||||||
@getId = {{createChannel.response.body.resource.id}}
|
|
||||||
|
|
||||||
GET {{channelsURL}}/channels/{{getId}}
|
|
||||||
Authorization: Bearer {{authToken}}
|
|
||||||
|
|
||||||
### Update a channel
|
|
||||||
@updateId = {{createChannel.response.body.resource.id}}
|
|
||||||
|
|
||||||
POST {{channelsURL}}/channels/{{updateId}}
|
|
||||||
Content-Type: application/json
|
|
||||||
Authorization: Bearer {{authToken}}
|
|
||||||
|
|
||||||
{
|
|
||||||
"resource": {
|
|
||||||
"name": "My channel",
|
|
||||||
"description": "Hello world",
|
|
||||||
"is_default": false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
### Delete a channel
|
|
||||||
|
|
||||||
@deleteId = {{createChannel.response.body.resource.id}}
|
|
||||||
|
|
||||||
DELETE {{channelsURL}}/channels/{{deleteId}}
|
|
||||||
Authorization: Bearer {{authToken}}
|
|
||||||
|
|
||||||
|
|
||||||
### Get all channel members
|
|
||||||
|
|
||||||
GET {{channelsURL}}/channels/{{getId}}/members?websockets=true
|
|
||||||
Content-Type: application/json
|
|
||||||
Authorization: Bearer {{authToken}}
|
|
||||||
|
|
||||||
|
|
||||||
### Add current user as member to a channel (join channel)
|
|
||||||
|
|
||||||
POST {{channelsURL}}/channels/{{getId}}/members
|
|
||||||
Content-Type: application/json
|
|
||||||
Authorization: Bearer {{authToken}}
|
|
||||||
|
|
||||||
{
|
|
||||||
"resource": {
|
|
||||||
"user_id": "{{currentUserId}}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
### Get a channel member
|
|
||||||
|
|
||||||
GET {{channelsURL}}/channels/{{getId}}/members/{{currentUserId}}
|
|
||||||
Content-Type: application/json
|
|
||||||
Authorization: Bearer {{authToken}}
|
|
||||||
|
|
||||||
### Update current channel member
|
|
||||||
|
|
||||||
POST {{channelsURL}}/channels/{{getId}}/members/{{currentUserId}}
|
|
||||||
Content-Type: application/json
|
|
||||||
Authorization: Bearer {{authToken}}
|
|
||||||
|
|
||||||
{
|
|
||||||
"resource": {
|
|
||||||
"favorite": true,
|
|
||||||
"notification_level": "none",
|
|
||||||
"hello": 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
### Current user quits the channel
|
|
||||||
|
|
||||||
DELETE {{channelsURL}}/channels/{{getId}}/members/{{currentUserId}}
|
|
||||||
Content-Type: application/json
|
|
||||||
Authorization: Bearer {{authToken}}
|
|
||||||
|
|
||||||
### Errors Tests
|
|
||||||
|
|
||||||
### Call without the JWT token should HTTP 401
|
|
||||||
|
|
||||||
GET {{channelsURL}}/channels
|
|
||||||
Content-Type: application/json
|
|
||||||
|
|
||||||
### Get a channel which may not exists
|
|
||||||
|
|
||||||
GET {{channelsURL}}/channels/0b0e1492-f596-46b9-a4fb-c12d71b2696e
|
|
||||||
Authorization: Bearer {{authToken}}
|
|
||||||
|
|
||||||
@@ -1,163 +0,0 @@
|
|||||||
@company_id = bcfe2f79-8e81-42a3-b551-3a32d49b2b4c
|
|
||||||
@workspace_id = 3328552c-5ccd-4172-b84a-d876d56aa71b
|
|
||||||
|
|
||||||
@baseURL = http://localhost:3000
|
|
||||||
@channelsURL = {{baseURL}}/internal/services/channels/v1/companies/{{company_id}}/workspaces/{{workspace_id}}
|
|
||||||
@directChannelsURL = {{baseURL}}/internal/services/channels/v1/companies/{{company_id}}/workspaces/direct
|
|
||||||
|
|
||||||
### Login as user 1
|
|
||||||
|
|
||||||
# @name login
|
|
||||||
GET {{baseURL}}/api/auth/login
|
|
||||||
|
|
||||||
@authToken1 = {{login.response.body.token}}
|
|
||||||
@userId1 = {{login.response.body.user.id}}
|
|
||||||
|
|
||||||
### Login as user 2
|
|
||||||
|
|
||||||
# @name login2
|
|
||||||
|
|
||||||
GET {{baseURL}}/api/auth/login
|
|
||||||
|
|
||||||
@authToken2 = {{login2.response.body.token}}
|
|
||||||
@userId2 = {{login2.response.body.user.id}}
|
|
||||||
|
|
||||||
### Login as user 3
|
|
||||||
|
|
||||||
# @name login3
|
|
||||||
|
|
||||||
GET {{baseURL}}/api/auth/login
|
|
||||||
|
|
||||||
@authToken3 = {{login3.response.body.token}}
|
|
||||||
@userId3 = {{login3.response.body.user.id}}
|
|
||||||
|
|
||||||
### Create a direct channel
|
|
||||||
|
|
||||||
# @name createDirectChannel
|
|
||||||
POST {{directChannelsURL}}/channels
|
|
||||||
Content-Type: application/json
|
|
||||||
Authorization: Bearer {{authToken1}}
|
|
||||||
|
|
||||||
{
|
|
||||||
"options": {
|
|
||||||
"members": ["{{userId2}}"]
|
|
||||||
},
|
|
||||||
"resource": {
|
|
||||||
"icon": "hello",
|
|
||||||
"description": "A direct channel",
|
|
||||||
"channel_group": "tdrive",
|
|
||||||
"is_default": false,
|
|
||||||
"archived": false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
### Direct channel details as user 1 should work
|
|
||||||
@directId = {{createDirectChannel.response.body.resource.id}}
|
|
||||||
|
|
||||||
GET {{directChannelsURL}}/channels/{{directId}}
|
|
||||||
Content-Type: application/json
|
|
||||||
Authorization: Bearer {{authToken1}}
|
|
||||||
|
|
||||||
### Direct channel details as user 2 (member) should work
|
|
||||||
|
|
||||||
GET {{directChannelsURL}}/channels/{{directId}}
|
|
||||||
Content-Type: application/json
|
|
||||||
Authorization: Bearer {{authToken2}}
|
|
||||||
|
|
||||||
### Direct channel details as user 3 (not member) should not work
|
|
||||||
|
|
||||||
GET {{directChannelsURL}}/channels/{{directId}}
|
|
||||||
Content-Type: application/json
|
|
||||||
Authorization: Bearer {{authToken3}}
|
|
||||||
|
|
||||||
### Direct channel members
|
|
||||||
|
|
||||||
GET {{directChannelsURL}}/channels/{{directId}}/members?websockets=true
|
|
||||||
Content-Type: application/json
|
|
||||||
Authorization: Bearer {{authToken1}}
|
|
||||||
|
|
||||||
### List direct channels for user in company
|
|
||||||
|
|
||||||
@authToken = {{login.response.body.token}}
|
|
||||||
GET {{directChannelsURL}}/channels?websockets=true&limit=5
|
|
||||||
Content-Type: application/json
|
|
||||||
Authorization: Bearer {{authToken1}}
|
|
||||||
|
|
||||||
### Update the direct channel description
|
|
||||||
|
|
||||||
POST {{directChannelsURL}}/channels/{{directId}}
|
|
||||||
Content-Type: application/json
|
|
||||||
Authorization: Bearer {{authToken1}}
|
|
||||||
|
|
||||||
{
|
|
||||||
"resource": {
|
|
||||||
"description": "A new direct channel description"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
### Update the direct channel name will do nothing since the name is useless
|
|
||||||
POST {{directChannelsURL}}/channels/{{directId}}
|
|
||||||
Content-Type: application/json
|
|
||||||
Authorization: Bearer {{authToken1}}
|
|
||||||
|
|
||||||
{
|
|
||||||
"resource": {
|
|
||||||
"name": "A new direct channel name"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
### Update the direct channel member settings for current user
|
|
||||||
|
|
||||||
POST {{directChannelsURL}}/channels/{{directId}}/members/{{userId1}}
|
|
||||||
Content-Type: application/json
|
|
||||||
Authorization: Bearer {{authToken1}}
|
|
||||||
|
|
||||||
{
|
|
||||||
"resource": {
|
|
||||||
"favorite": true,
|
|
||||||
"notification_level": "all"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
### Update another user member settings should fail
|
|
||||||
|
|
||||||
POST {{directChannelsURL}}/channels/{{directId}}/members/{{userId2}}
|
|
||||||
Content-Type: application/json
|
|
||||||
Authorization: Bearer {{authToken1}}
|
|
||||||
|
|
||||||
{
|
|
||||||
"resource": {
|
|
||||||
"favorite": true,
|
|
||||||
"notification_level": "none"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
### Get member settings as user1
|
|
||||||
|
|
||||||
GET {{directChannelsURL}}/channels/{{directId}}/members/{{userId1}}
|
|
||||||
Content-Type: application/json
|
|
||||||
Authorization: Bearer {{authToken1}}
|
|
||||||
|
|
||||||
### Get member settings as user2
|
|
||||||
|
|
||||||
GET {{directChannelsURL}}/channels/{{directId}}/members/{{userId2}}
|
|
||||||
Content-Type: application/json
|
|
||||||
Authorization: Bearer {{authToken2}}
|
|
||||||
|
|
||||||
### Get other member settings should fail
|
|
||||||
|
|
||||||
GET {{directChannelsURL}}/channels/{{directId}}/members/{{userId2}}
|
|
||||||
Content-Type: application/json
|
|
||||||
Authorization: Bearer {{authToken1}}
|
|
||||||
|
|
||||||
### Leave a direct channel of another user should fail
|
|
||||||
|
|
||||||
DELETE {{directChannelsURL}}/channels/{{directId}}/members/{{userId2}}
|
|
||||||
Content-Type: application/json
|
|
||||||
Authorization: Bearer {{authToken1}}
|
|
||||||
|
|
||||||
### Leave a direct channel should not fail
|
|
||||||
|
|
||||||
DELETE {{directChannelsURL}}/channels/{{directId}}/members/{{userId1}}
|
|
||||||
Content-Type: application/json
|
|
||||||
Authorization: Bearer {{authToken1}}
|
|
||||||
@@ -0,0 +1,251 @@
|
|||||||
|
# The task is:
|
||||||
|
# to find all the files that I havee access to, it shoud be a doc files,
|
||||||
|
# that was modified during the last week and share with my by Diana Potoking
|
||||||
|
|
||||||
|
|
||||||
|
### Drop index
|
||||||
|
DELETE https://localhost:9200/tdrive_files_extended
|
||||||
|
|
||||||
|
### Delete Index
|
||||||
|
DELETE https://localhost:9200/drive_files
|
||||||
|
|
||||||
|
### Create index
|
||||||
|
PUT https://localhost:9200/tdrive_files_extended
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
{
|
||||||
|
"drive_files": {
|
||||||
|
"aliases": {},
|
||||||
|
"mappings": {
|
||||||
|
"_source": {
|
||||||
|
"enabled": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Check that it is created
|
||||||
|
GET https://localhost:9200/_cat/indices
|
||||||
|
|
||||||
|
### GET index info
|
||||||
|
GET https://localhost:9200/drive_files
|
||||||
|
|
||||||
|
### GET data to the index
|
||||||
|
GET https://localhost:9200/drive_files/_doc/8c03c5a1-0146-11ee-82d9-f503f8a58e9d
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
### Put data to the index
|
||||||
|
PUT https://localhost:9200/tdrive_files_extended/_doc/0001
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
{
|
||||||
|
"file_name": "file0001.txt",
|
||||||
|
"access_info": [
|
||||||
|
{
|
||||||
|
"type": "user",
|
||||||
|
"id": "user01",
|
||||||
|
"level": "read",
|
||||||
|
"grantor": "user02"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
###
|
||||||
|
PUT https://localhost:9200/tdrive_files_extended/_doc/8c03c5a1-0146-11ee-82d9-f503f8a58e9d
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
{
|
||||||
|
"file_name": "file0002.png",
|
||||||
|
"access_info": [
|
||||||
|
{
|
||||||
|
"type": "user",
|
||||||
|
"id": "user01",
|
||||||
|
"level": "read",
|
||||||
|
"grantor": "user03"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
### Search all in the index by last_modified
|
||||||
|
GET https://localhost:9200/drive_files/_search
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
{
|
||||||
|
"query": {
|
||||||
|
|
||||||
|
"bool": {
|
||||||
|
"boost": 1,
|
||||||
|
"must": [
|
||||||
|
{
|
||||||
|
"range":
|
||||||
|
{
|
||||||
|
"last_modified": {
|
||||||
|
"gte": "0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"range":
|
||||||
|
{
|
||||||
|
"last_modified": {
|
||||||
|
"lte": "1685958426348"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bool": {
|
||||||
|
"should": [
|
||||||
|
{
|
||||||
|
"match": {
|
||||||
|
"access_entities": {
|
||||||
|
"query": "edfb30e0-0385-11ee-80e8-41892804174d",
|
||||||
|
"operator": "AND"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"match": {
|
||||||
|
"access_entities": {
|
||||||
|
"query": "edd447f1-0385-11ee-80e8-41892804174d",
|
||||||
|
"operator": "AND"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"minimum_should_match": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bool": {
|
||||||
|
"should": [
|
||||||
|
{
|
||||||
|
"match": {
|
||||||
|
"company_id": {
|
||||||
|
"query": "edd447f1-0385-11ee-80e8-41892804174d",
|
||||||
|
"operator": "AND"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"minimum_should_match": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
### Search all in the index and order by name
|
||||||
|
GET https://localhost:9200/drive_files/_search
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
{
|
||||||
|
"query": {
|
||||||
|
"bool": {
|
||||||
|
"boost": 1,
|
||||||
|
"must": [
|
||||||
|
{
|
||||||
|
"bool": {
|
||||||
|
"should": [
|
||||||
|
{
|
||||||
|
"match": {
|
||||||
|
"access_entities": {
|
||||||
|
"query": "12fb6a40-03b3-11ee-af31-a569970b7f74",
|
||||||
|
"operator": "AND"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"match": {
|
||||||
|
"access_entities": {
|
||||||
|
"query": "12f290a1-03b3-11ee-af31-a569970b7f74",
|
||||||
|
"operator": "AND"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"minimum_should_match": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bool": {
|
||||||
|
"should": [
|
||||||
|
{
|
||||||
|
"match": {
|
||||||
|
"company_id": {
|
||||||
|
"query": "12f290a1-03b3-11ee-af31-a569970b7f74",
|
||||||
|
"operator": "AND"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"minimum_should_match": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"sort": [
|
||||||
|
{
|
||||||
|
"name": "asc"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
### Search all in the index and order by name
|
||||||
|
GET https://localhost:9200/drive_files/_search
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
{
|
||||||
|
"query": {
|
||||||
|
"bool": {
|
||||||
|
"boost": 1,
|
||||||
|
"must": [
|
||||||
|
{
|
||||||
|
"bool": {
|
||||||
|
"should": [
|
||||||
|
{
|
||||||
|
"match": {
|
||||||
|
"access_entities": {
|
||||||
|
"query": "4090dce0-03ba-11ee-97d7-fb8fa15d86a1",
|
||||||
|
"operator": "AND"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"match": {
|
||||||
|
"access_entities": {
|
||||||
|
"query": "40808931-03ba-11ee-97d7-fb8fa15d86a1",
|
||||||
|
"operator": "AND"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"minimum_should_match": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bool": {
|
||||||
|
"should": [
|
||||||
|
{
|
||||||
|
"match": {
|
||||||
|
"company_id": {
|
||||||
|
"query": "40808931-03ba-11ee-97d7-fb8fa15d86a1",
|
||||||
|
"operator": "AND"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"minimum_should_match": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,108 +0,0 @@
|
|||||||
@company_id = bcfe2f79-8e81-42a3-b551-3a32d49b2b4c
|
|
||||||
@workspace_id = 3328552c-5ccd-4172-b84a-d876d56aa71b
|
|
||||||
@baseURL = http://localhost:3000
|
|
||||||
@channelsURL = {{baseURL}}/internal/services/channels/v1/companies/{{company_id}}/workspaces/{{workspace_id}}
|
|
||||||
@directChannelsURL = {{baseURL}}/internal/services/channels/v1/companies/{{company_id}}/workspaces/direct
|
|
||||||
|
|
||||||
### Login as user 1
|
|
||||||
|
|
||||||
# @name login
|
|
||||||
GET {{baseURL}}/api/auth/login
|
|
||||||
|
|
||||||
@authTokenUser1 = {{login.response.body.token}}
|
|
||||||
@currentUserId1 = {{login.response.body.user.id}}
|
|
||||||
|
|
||||||
### Login as user 2
|
|
||||||
|
|
||||||
# @name login2
|
|
||||||
GET {{baseURL}}/api/auth/login
|
|
||||||
|
|
||||||
@authTokenUser2 = {{login2.response.body.token}}
|
|
||||||
@currentUserId2 = {{login2.response.body.user.id}}
|
|
||||||
|
|
||||||
### User 1 creates a private channel
|
|
||||||
|
|
||||||
# @name createChannel
|
|
||||||
POST {{channelsURL}}/channels
|
|
||||||
Content-Type: application/json
|
|
||||||
Authorization: Bearer {{authTokenUser1}}
|
|
||||||
|
|
||||||
{
|
|
||||||
"resource": {
|
|
||||||
"name": "My private channel",
|
|
||||||
"icon": "tdrive logo",
|
|
||||||
"description": "This channel allow tdrive's team to chat easily",
|
|
||||||
"channel_group": "tdrive",
|
|
||||||
"visibility": "private",
|
|
||||||
"is_default": true,
|
|
||||||
"archived": false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
### Get the private channel
|
|
||||||
|
|
||||||
@getId = {{createChannel.response.body.resource.id}}
|
|
||||||
|
|
||||||
GET {{channelsURL}}/channels/{{getId}}
|
|
||||||
Authorization: Bearer {{authTokenUser1}}
|
|
||||||
|
|
||||||
### Get all channel members
|
|
||||||
|
|
||||||
GET {{channelsURL}}/channels/{{getId}}/members?websockets=true
|
|
||||||
Content-Type: application/json
|
|
||||||
Authorization: Bearer {{authTokenUser1}}
|
|
||||||
|
|
||||||
|
|
||||||
### The user 2 tries to join the private channel, this should reject
|
|
||||||
|
|
||||||
POST {{channelsURL}}/channels/{{getId}}/members
|
|
||||||
Content-Type: application/json
|
|
||||||
Authorization: Bearer {{authTokenUser2}}
|
|
||||||
|
|
||||||
{
|
|
||||||
"resource": {
|
|
||||||
"user_id": "{{currentUserId2}}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
### The user 1 adds the user 2 as channel member, this should be OK
|
|
||||||
|
|
||||||
POST {{channelsURL}}/channels/{{getId}}/members
|
|
||||||
Content-Type: application/json
|
|
||||||
Authorization: Bearer {{authTokenUser1}}
|
|
||||||
|
|
||||||
{
|
|
||||||
"resource": {
|
|
||||||
"user_id": "{{currentUserId2}}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
### User 2 lists his channels
|
|
||||||
|
|
||||||
GET {{channelsURL}}/channels?websockets=true&mine=true&limit=5
|
|
||||||
Content-Type: application/json
|
|
||||||
Authorization: Bearer {{authTokenUser2}}
|
|
||||||
|
|
||||||
### Get all channel members as user 1
|
|
||||||
|
|
||||||
GET {{channelsURL}}/channels/{{getId}}/members?websockets=true
|
|
||||||
Content-Type: application/json
|
|
||||||
Authorization: Bearer {{authTokenUser1}}
|
|
||||||
|
|
||||||
### Get all channel members as user 2
|
|
||||||
|
|
||||||
GET {{channelsURL}}/channels/{{getId}}/members?websockets=true
|
|
||||||
Content-Type: application/json
|
|
||||||
Authorization: Bearer {{authTokenUser2}}
|
|
||||||
|
|
||||||
### User 1 leaves the channel he created
|
|
||||||
|
|
||||||
DELETE {{channelsURL}}/channels/{{getId}}/members/{{currentUserId1}}
|
|
||||||
Content-Type: application/json
|
|
||||||
Authorization: Bearer {{authTokenUser1}}
|
|
||||||
|
|
||||||
### User 2 leaves the channel: Error since he is the last member, he can not leave
|
|
||||||
|
|
||||||
DELETE {{channelsURL}}/channels/{{getId}}/members/{{currentUserId2}}
|
|
||||||
Content-Type: application/json
|
|
||||||
Authorization: Bearer {{authTokenUser2}}
|
|
||||||
@@ -1,70 +0,0 @@
|
|||||||
@company_id = bcfe2f79-8e81-42a3-b551-3a32d49b2b4c
|
|
||||||
@workspace_id = 3328552c-5ccd-4172-b84a-d876d56aa71b
|
|
||||||
@user_id = 3328552c-5ccd-4172-b84a-d876d56aa71c
|
|
||||||
|
|
||||||
@baseURL = http://localhost:3000
|
|
||||||
@channelsURL = {{baseURL}}/internal/services/channels/v1/companies/{{company_id}}/workspaces/{{workspace_id}}
|
|
||||||
@directChannelsURL = {{baseURL}}/internal/services/channels/v1/companies/{{company_id}}/workspaces/direct
|
|
||||||
|
|
||||||
# @name login
|
|
||||||
GET {{baseURL}}/api/auth/login
|
|
||||||
|
|
||||||
@authToken = {{login.response.body.token}}
|
|
||||||
@currentUserId = {{login.response.body.user.id}}
|
|
||||||
|
|
||||||
### List workspace channels with all websockets
|
|
||||||
|
|
||||||
GET {{channelsURL}}/channels?websockets=true&limit=5
|
|
||||||
Content-Type: application/json
|
|
||||||
Authorization: Bearer {{authToken}}
|
|
||||||
|
|
||||||
### List user channels with all websockets
|
|
||||||
|
|
||||||
@authToken = {{login.response.body.token}}
|
|
||||||
GET {{channelsURL}}/channels?websockets=true&mine=true&limit=5
|
|
||||||
Content-Type: application/json
|
|
||||||
Authorization: Bearer {{authToken}}
|
|
||||||
|
|
||||||
### Create a private channel
|
|
||||||
|
|
||||||
# @name createChannel
|
|
||||||
POST {{channelsURL}}/channels
|
|
||||||
Content-Type: application/json
|
|
||||||
Authorization: Bearer {{authToken}}
|
|
||||||
|
|
||||||
{
|
|
||||||
"resource": {
|
|
||||||
"name": "My private channel",
|
|
||||||
"icon": "tdrive logo",
|
|
||||||
"description": "A private channel",
|
|
||||||
"channel_group": "tdrive",
|
|
||||||
"visibility": "private",
|
|
||||||
"is_default": true,
|
|
||||||
"archived": false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
### Get the private channel
|
|
||||||
|
|
||||||
@getId = {{createChannel.response.body.resource.id}}
|
|
||||||
|
|
||||||
GET {{channelsURL}}/channels/{{getId}}
|
|
||||||
Authorization: Bearer {{authToken}}
|
|
||||||
|
|
||||||
### Get members of the private channel
|
|
||||||
|
|
||||||
GET {{channelsURL}}/channels/{{getId}}/members?websockets=true
|
|
||||||
Content-Type: application/json
|
|
||||||
Authorization: Bearer {{authToken}}
|
|
||||||
|
|
||||||
### Get current user as member
|
|
||||||
|
|
||||||
GET {{channelsURL}}/channels/{{getId}}/members/{{currentUserId}}
|
|
||||||
Content-Type: application/json
|
|
||||||
Authorization: Bearer {{authToken}}
|
|
||||||
|
|
||||||
### Current user quits the private channel will fail because he is the only one in it
|
|
||||||
|
|
||||||
DELETE {{channelsURL}}/channels/{{getId}}/members/{{currentUserId}}
|
|
||||||
Content-Type: application/json
|
|
||||||
Authorization: Bearer {{authToken}}
|
|
||||||
@@ -1,108 +0,0 @@
|
|||||||
@company_id = bcfe2f79-8e81-42a3-b551-3a32d49b2b4c
|
|
||||||
@workspace_id = 3328552c-5ccd-4172-b84a-d876d56aa71b
|
|
||||||
@baseURL = http://localhost:3000
|
|
||||||
@channelsURL = {{baseURL}}/internal/services/channels/v1/companies/{{company_id}}/workspaces/{{workspace_id}}
|
|
||||||
@directChannelsURL = {{baseURL}}/internal/services/channels/v1/companies/{{company_id}}/workspaces/direct
|
|
||||||
|
|
||||||
### Login as user 1
|
|
||||||
|
|
||||||
# @name login
|
|
||||||
GET {{baseURL}}/api/auth/login
|
|
||||||
|
|
||||||
@authTokenUser1 = {{login.response.body.token}}
|
|
||||||
@currentUserId1 = {{login.response.body.user.id}}
|
|
||||||
|
|
||||||
### Login as user 2
|
|
||||||
|
|
||||||
# @name login2
|
|
||||||
GET {{baseURL}}/api/auth/login
|
|
||||||
|
|
||||||
@authTokenUser2 = {{login2.response.body.token}}
|
|
||||||
@currentUserId2 = {{login2.response.body.user.id}}
|
|
||||||
|
|
||||||
### User 1 creates a public channel
|
|
||||||
|
|
||||||
# @name createChannel
|
|
||||||
POST {{channelsURL}}/channels
|
|
||||||
Content-Type: application/json
|
|
||||||
Authorization: Bearer {{authTokenUser1}}
|
|
||||||
|
|
||||||
{
|
|
||||||
"resource": {
|
|
||||||
"name": "A public channel",
|
|
||||||
"icon": "tdrive logo",
|
|
||||||
"description": "This channel allow tdrive's team to chat easily",
|
|
||||||
"channel_group": "tdrive",
|
|
||||||
"visibility": "public",
|
|
||||||
"is_default": true,
|
|
||||||
"archived": false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
### Get the public channel
|
|
||||||
|
|
||||||
@getId = {{createChannel.response.body.resource.id}}
|
|
||||||
|
|
||||||
GET {{channelsURL}}/channels/{{getId}}
|
|
||||||
Authorization: Bearer {{authTokenUser1}}
|
|
||||||
|
|
||||||
### Get all channel members
|
|
||||||
|
|
||||||
GET {{channelsURL}}/channels/{{getId}}/members?websockets=true
|
|
||||||
Content-Type: application/json
|
|
||||||
Authorization: Bearer {{authTokenUser1}}
|
|
||||||
|
|
||||||
|
|
||||||
### The user 2 joins the public channel, this should be OK
|
|
||||||
|
|
||||||
POST {{channelsURL}}/channels/{{getId}}/members
|
|
||||||
Content-Type: application/json
|
|
||||||
Authorization: Bearer {{authTokenUser2}}
|
|
||||||
|
|
||||||
{
|
|
||||||
"resource": {
|
|
||||||
"user_id": "{{currentUserId2}}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
### Get all channel members, there are now 2 members
|
|
||||||
|
|
||||||
GET {{channelsURL}}/channels/{{getId}}/members?websockets=true
|
|
||||||
Content-Type: application/json
|
|
||||||
Authorization: Bearer {{authTokenUser1}}
|
|
||||||
|
|
||||||
### User 1 lists his channels
|
|
||||||
|
|
||||||
GET {{channelsURL}}/channels?mine=true
|
|
||||||
Content-Type: application/json
|
|
||||||
Authorization: Bearer {{authTokenUser1}}
|
|
||||||
|
|
||||||
### User 2 lists his channels
|
|
||||||
|
|
||||||
GET {{channelsURL}}/channels?mine=true
|
|
||||||
Content-Type: application/json
|
|
||||||
Authorization: Bearer {{authTokenUser2}}
|
|
||||||
|
|
||||||
### User 2 leaves the channel
|
|
||||||
|
|
||||||
DELETE {{channelsURL}}/channels/{{getId}}/members/{{currentUserId2}}
|
|
||||||
Content-Type: application/json
|
|
||||||
Authorization: Bearer {{authTokenUser2}}
|
|
||||||
|
|
||||||
### Get all channel members, there are now 1 member, the initial one
|
|
||||||
|
|
||||||
GET {{channelsURL}}/channels/{{getId}}/members?websockets=true
|
|
||||||
Content-Type: application/json
|
|
||||||
Authorization: Bearer {{authTokenUser1}}
|
|
||||||
|
|
||||||
### List channels as user 2, current channel appears since it is public
|
|
||||||
|
|
||||||
GET {{channelsURL}}/channels
|
|
||||||
Content-Type: application/json
|
|
||||||
Authorization: Bearer {{authTokenUser2}}
|
|
||||||
|
|
||||||
### User 2 list his public channels, current channel does not appear since he left it
|
|
||||||
|
|
||||||
GET {{channelsURL}}/channels?mine=true
|
|
||||||
Content-Type: application/json
|
|
||||||
Authorization: Bearer {{authTokenUser2}}
|
|
||||||
@@ -1,85 +0,0 @@
|
|||||||
@company_id = bcfe2f79-8e81-42a3-b551-3a32d49b2b4c
|
|
||||||
@workspace_id = 3328552c-5ccd-4172-b84a-d876d56aa71a
|
|
||||||
|
|
||||||
|
|
||||||
@baseURL = http://localhost:3000
|
|
||||||
@tabsURL = {{baseURL}}/internal/services/channels/v1/companies/{{company_id}}/workspaces/{{workspace_id}}/channels
|
|
||||||
@channelsURL = {{baseURL}}/internal/services/channels/v1/companies/{{company_id}}/workspaces/{{workspace_id}}
|
|
||||||
|
|
||||||
|
|
||||||
# @name login
|
|
||||||
GET {{baseURL}}/api/auth/login
|
|
||||||
|
|
||||||
@authToken = {{login.response.body.token}}
|
|
||||||
@currentUserId = {{login.response.body.user.id}}
|
|
||||||
|
|
||||||
|
|
||||||
### List channel's tab with all websockets
|
|
||||||
GET {{tabsURL}}/{{channelId}}/tabs?websockets=true&limit=5
|
|
||||||
Content-Type: application/json
|
|
||||||
Authorization: Bearer {{authToken}}
|
|
||||||
|
|
||||||
|
|
||||||
### Create a channel
|
|
||||||
# @name createChannel
|
|
||||||
POST {{channelsURL}}/channels
|
|
||||||
Content-Type: application/json
|
|
||||||
Authorization: Bearer {{authToken}}
|
|
||||||
|
|
||||||
{
|
|
||||||
"resource": {
|
|
||||||
"name": "My channel",
|
|
||||||
"icon": "tdrive logo",
|
|
||||||
"description": "This channel allow tdrive's team to chat easily",
|
|
||||||
"channel_group": "tdrive",
|
|
||||||
"visibility": "public",
|
|
||||||
"is_default": true,
|
|
||||||
"archived": false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
### Create a tab
|
|
||||||
# @name createTab
|
|
||||||
@channelId = {{createChannel.response.body.resource.id}}
|
|
||||||
|
|
||||||
POST {{tabsURL}}/{{channelId}}/tabs
|
|
||||||
Content-Type: application/json
|
|
||||||
Authorization: Bearer {{authToken}}
|
|
||||||
|
|
||||||
{
|
|
||||||
"resource": {
|
|
||||||
// WARNING : special caracter in tab's name does not work
|
|
||||||
"name": "My tabs name",
|
|
||||||
"configuration": "JSON"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
### Get a single tab
|
|
||||||
@getId = {{createTab.response.body.resource.id}}
|
|
||||||
|
|
||||||
GET {{tabsURL}}/{{channelId}}/tabs/{{getId}}
|
|
||||||
Authorization: Bearer {{authToken}}
|
|
||||||
|
|
||||||
|
|
||||||
### Update a tab
|
|
||||||
@updateId = {{createTab.response.body.resource.id}}
|
|
||||||
|
|
||||||
POST {{tabsURL}}/{{channelId}}/tabs/{{updateId}}
|
|
||||||
Content-Type: application/json
|
|
||||||
Authorization: Bearer {{authToken}}
|
|
||||||
|
|
||||||
{
|
|
||||||
"resource": {
|
|
||||||
"name": "My tab updated",
|
|
||||||
"configuration": "JSON"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
### Delete a tab
|
|
||||||
@deleteId = {{createTab.response.body.resource.id}}
|
|
||||||
|
|
||||||
DELETE {{tabsURL}}/{{channelId}}/tabs/{{deleteId}}
|
|
||||||
Authorization: Bearer {{authToken}}
|
|
||||||
+158
@@ -0,0 +1,158 @@
|
|||||||
|
import "reflect-metadata";
|
||||||
|
import {describe, it} from "@jest/globals";
|
||||||
|
import {buildSearchQuery} from "../../../../../../../src/core/platform/services/search/adapters/elasticsearch/search";
|
||||||
|
import {DriveFile} from "../../../../../../../src/services/documents/entities/drive-file";
|
||||||
|
import {FindFilter, FindOptions} from "../../../../../../../src/core/platform/services/search/api";
|
||||||
|
import {
|
||||||
|
comparisonType
|
||||||
|
} from "../../../../../../../src/core/platform/services/database/services/orm/repository/repository";
|
||||||
|
|
||||||
|
describe("ES Query Builder", () => {
|
||||||
|
|
||||||
|
describe("The intervals section of the search query", () => {
|
||||||
|
|
||||||
|
it("'lte' section become a proper 'range' array in ES query", async () => {
|
||||||
|
//given::
|
||||||
|
const filters: FindFilter = {};
|
||||||
|
const options: FindOptions = {
|
||||||
|
$lte: [["last_modified", 10] as comparisonType]
|
||||||
|
};
|
||||||
|
|
||||||
|
//when
|
||||||
|
let esReq = buildSearchQuery(DriveFile, filters, options);
|
||||||
|
|
||||||
|
//then
|
||||||
|
const expected: any = {
|
||||||
|
bool: {
|
||||||
|
boost: 1,
|
||||||
|
must: [
|
||||||
|
{
|
||||||
|
range: {
|
||||||
|
last_modified: {
|
||||||
|
lte: 10
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const query = esReq.esParams.body["query"];
|
||||||
|
expect(query).not.toBeNull();
|
||||||
|
expect(query).toEqual(expected);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("'lte' section shouldn't exist if some params are missing", async () => {
|
||||||
|
//given::
|
||||||
|
const filters: FindFilter = {};
|
||||||
|
const options: FindOptions = {
|
||||||
|
$lte: []
|
||||||
|
};
|
||||||
|
|
||||||
|
//when
|
||||||
|
let esReq = buildSearchQuery(DriveFile, filters, options);
|
||||||
|
|
||||||
|
//then
|
||||||
|
const expected: any = {
|
||||||
|
bool: {
|
||||||
|
boost: 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const query = esReq.esParams.body["query"];
|
||||||
|
expect(query).not.toBeNull();
|
||||||
|
expect(query).toEqual(expected);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("'gte' section become a proper 'range' array in ES query", async () => {
|
||||||
|
//given::
|
||||||
|
const filters: FindFilter = {};
|
||||||
|
const options: FindOptions = {
|
||||||
|
$gte: [["last_modified", 10] as comparisonType]
|
||||||
|
};
|
||||||
|
|
||||||
|
//when
|
||||||
|
let esReq = buildSearchQuery(DriveFile, filters, options);
|
||||||
|
|
||||||
|
//then
|
||||||
|
const expected: any = {
|
||||||
|
bool: {
|
||||||
|
boost: 1,
|
||||||
|
must: [
|
||||||
|
{
|
||||||
|
range: {
|
||||||
|
last_modified: {
|
||||||
|
gte: 10
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const query = esReq.esParams.body["query"];
|
||||||
|
expect(query).not.toBeNull();
|
||||||
|
expect(query).toEqual(expected);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("'gt' section become a proper 'range' array in ES query", async () => {
|
||||||
|
//given::
|
||||||
|
const filters: FindFilter = {};
|
||||||
|
const options: FindOptions = {
|
||||||
|
$gt: [["last_modified", 10] as comparisonType]
|
||||||
|
};
|
||||||
|
|
||||||
|
//when
|
||||||
|
let esReq = buildSearchQuery(DriveFile, filters, options);
|
||||||
|
|
||||||
|
//then
|
||||||
|
const expected: any = {
|
||||||
|
bool: {
|
||||||
|
boost: 1,
|
||||||
|
must: [
|
||||||
|
{
|
||||||
|
range: {
|
||||||
|
last_modified: {
|
||||||
|
gt: 10
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const query = esReq.esParams.body["query"];
|
||||||
|
expect(query).not.toBeNull();
|
||||||
|
expect(query).toEqual(expected);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("'lt' section become a proper 'range' array in ES query", async () => {
|
||||||
|
//given::
|
||||||
|
const filters: FindFilter = {};
|
||||||
|
const options: FindOptions = {
|
||||||
|
$lt: [["last_modified", 10] as comparisonType]
|
||||||
|
};
|
||||||
|
|
||||||
|
//when
|
||||||
|
let esReq = buildSearchQuery(DriveFile, filters, options);
|
||||||
|
|
||||||
|
//then
|
||||||
|
const expected: any = {
|
||||||
|
bool: {
|
||||||
|
boost: 1,
|
||||||
|
must: [
|
||||||
|
{
|
||||||
|
range: {
|
||||||
|
last_modified: {
|
||||||
|
lt: 10
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const query = esReq.esParams.body["query"];
|
||||||
|
expect(query).not.toBeNull();
|
||||||
|
expect(query).toEqual(expected);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user