🔀 Merge remote-tracking branch 'origin/main' into release/v1.0.5
This commit is contained in:
@@ -12,6 +12,12 @@ export const logger = pino({
|
||||
mixin() {
|
||||
return executionStorage.getStore() ? executionStorage.getStore() : {};
|
||||
},
|
||||
formatters: {
|
||||
level(label: string) {
|
||||
return { level: label.toUpperCase() };
|
||||
},
|
||||
},
|
||||
serializers: pino.stdSerializers,
|
||||
});
|
||||
|
||||
export const getLogger = (name?: string): TdriveLogger =>
|
||||
|
||||
@@ -28,7 +28,7 @@ export async function buildDependenciesTree(
|
||||
`The component dependency ${dependencyName} has not been found for component ${name}`,
|
||||
);
|
||||
} else {
|
||||
console.warn(
|
||||
logger.warn(
|
||||
`(warning) The component dependency ${dependencyName} has not been found for component ${name} it will be imported asynchronously`,
|
||||
);
|
||||
|
||||
|
||||
@@ -209,7 +209,7 @@ export default class MongoSearch extends SearchAdapter implements SearchAdapterI
|
||||
const { query, sort, project } = buildSearchQuery<EntityType>(entityType, filters, options);
|
||||
|
||||
logger.info(`Search query: ${JSON.stringify(query)}`);
|
||||
console.log(query);
|
||||
logger.info(query);
|
||||
|
||||
const sortMapped: any = sort
|
||||
? Object.fromEntries(
|
||||
|
||||
+2
-1
@@ -1,6 +1,7 @@
|
||||
import { buildSelectQuery } from "../../../database/services/orm/connectors/mongodb/query-builder";
|
||||
import { EntityTarget, FindFilter, FindOptions, getEntityDefinition } from "../../api";
|
||||
import { asciiFold } from "../utils";
|
||||
import { logger } from "../../../../framework";
|
||||
|
||||
export function buildSearchQuery<Entity>(
|
||||
entityType: EntityTarget<Entity>,
|
||||
@@ -15,7 +16,7 @@ export function buildSearchQuery<Entity>(
|
||||
try {
|
||||
query = buildSelectQuery(entityType, filters, options);
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
logger.info(e);
|
||||
}
|
||||
let sort: any = {};
|
||||
|
||||
|
||||
@@ -2,8 +2,10 @@ import * as Minio from "minio";
|
||||
import { logger } from "../../../../../../core/platform/framework";
|
||||
import { Readable } from "stream";
|
||||
import { StorageConnectorAPI, WriteMetadata } from "../../provider";
|
||||
import { randomUUID } from "crypto";
|
||||
|
||||
export type S3Configuration = {
|
||||
id: string;
|
||||
bucket: string;
|
||||
region: string;
|
||||
endPoint: string;
|
||||
@@ -17,10 +19,20 @@ export type S3Configuration = {
|
||||
export default class S3ConnectorService implements StorageConnectorAPI {
|
||||
client: Minio.Client;
|
||||
minioConfiguration: S3Configuration;
|
||||
id: string;
|
||||
|
||||
constructor(S3Configuration: S3Configuration) {
|
||||
this.client = new Minio.Client(S3Configuration);
|
||||
this.minioConfiguration = S3Configuration;
|
||||
this.id = this.minioConfiguration.id;
|
||||
if (!this.id) {
|
||||
this.id = randomUUID();
|
||||
logger.info(`Identifier for S3 storage haven't been set, initializing to '${this.id}'`);
|
||||
}
|
||||
}
|
||||
|
||||
getId() {
|
||||
return this.id;
|
||||
}
|
||||
|
||||
write(path: string, stream: Readable): Promise<WriteMetadata> {
|
||||
@@ -101,7 +113,7 @@ export default class S3ConnectorService implements StorageConnectorAPI {
|
||||
break;
|
||||
}
|
||||
} catch (e) {
|
||||
logger.error("Error getting information from S3", e);
|
||||
logger.error(e, `Error getting information from S3 for path: ${path}`);
|
||||
}
|
||||
|
||||
if (i === tries) {
|
||||
|
||||
@@ -5,16 +5,28 @@ import { rm } from "fs/promises"; // Do not change the import, this is not the s
|
||||
import { StorageConnectorAPI, WriteMetadata } from "../../provider";
|
||||
import fs from "fs";
|
||||
import { logger } from "../../../../framework/logger";
|
||||
import { randomUUID } from "crypto";
|
||||
|
||||
export type LocalConfiguration = {
|
||||
id: string;
|
||||
path: string;
|
||||
};
|
||||
|
||||
export default class LocalConnectorService implements StorageConnectorAPI {
|
||||
id: string;
|
||||
configuration: LocalConfiguration;
|
||||
|
||||
constructor(localConfiguration: LocalConfiguration) {
|
||||
this.configuration = localConfiguration;
|
||||
this.id = this.configuration.id;
|
||||
if (!this.id) {
|
||||
this.id = randomUUID();
|
||||
logger.info(`Identifier for local storage haven't been set, initializing to '${this.id}'`);
|
||||
}
|
||||
}
|
||||
|
||||
getId() {
|
||||
return this.id;
|
||||
}
|
||||
|
||||
write(relativePath: string, stream: Readable): Promise<WriteMetadata> {
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
import { Readable, Stream } from "stream";
|
||||
import { ExecutionContext } from "../../framework/api/crud-service";
|
||||
import {
|
||||
DeleteOptions,
|
||||
ReadOptions,
|
||||
StorageConnectorAPI,
|
||||
WriteMetadata,
|
||||
WriteOptions,
|
||||
} from "../storage/provider";
|
||||
|
||||
export class DefaultStorageStrategy implements StorageConnectorAPI {
|
||||
private connector: StorageConnectorAPI;
|
||||
|
||||
constructor(connector: StorageConnectorAPI) {
|
||||
this.connector = connector;
|
||||
}
|
||||
|
||||
getId() {
|
||||
return this.connector.getId();
|
||||
}
|
||||
|
||||
write = (
|
||||
path: string,
|
||||
stream: Stream,
|
||||
options?: WriteOptions,
|
||||
context?: ExecutionContext,
|
||||
): Promise<WriteMetadata> => {
|
||||
return this.connector.write(path, stream, options, context);
|
||||
};
|
||||
|
||||
read = (path: string, options?: ReadOptions, context?: ExecutionContext): Promise<Readable> => {
|
||||
return this.connector.read(path, options, context);
|
||||
};
|
||||
|
||||
exists = (path: string, options?: ReadOptions, context?: ExecutionContext): Promise<boolean> => {
|
||||
return this.connector.exists(path, options, context);
|
||||
};
|
||||
|
||||
remove = (
|
||||
path: string,
|
||||
options?: DeleteOptions,
|
||||
context?: ExecutionContext,
|
||||
): Promise<boolean> => {
|
||||
return this.connector.remove(path, options, context);
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
export class FileNotFountException extends Error {}
|
||||
export class WriteFileException extends Error {}
|
||||
@@ -0,0 +1,163 @@
|
||||
import { Readable, Stream, PassThrough } from "stream";
|
||||
import {
|
||||
DeleteOptions,
|
||||
ReadOptions,
|
||||
StorageConnectorAPI,
|
||||
WriteMetadata,
|
||||
WriteOptions,
|
||||
} from "../storage/provider";
|
||||
import { logger } from "../../../platform/framework";
|
||||
import { FileNotFountException, WriteFileException } from "./exceptions";
|
||||
|
||||
/**
|
||||
* OneOfStorageStrategy is responsible for managing multiple storage backends.
|
||||
* It writes to, reads from, removes, and checks for the existence of files
|
||||
* across multiple storage systems.
|
||||
*
|
||||
* WARNING: Be careful using this strategy. There is a very big probability that
|
||||
* all storages won't be synchronized. You need to use external synchronization
|
||||
* tools like rclone to keep them in sync.
|
||||
* As far as all the objects in a storage are immutable you won't have inconsistent
|
||||
* state of the object except it could've been deleted, and it's still exists in one
|
||||
* of the storages. So you will need to have external procedures to clean up storages
|
||||
* and make "garbage" collection.
|
||||
*/
|
||||
export class OneOfStorageStrategy implements StorageConnectorAPI {
|
||||
id: string;
|
||||
|
||||
constructor(private readonly storages: StorageConnectorAPI[]) {}
|
||||
|
||||
getId() {
|
||||
if (!this.id) {
|
||||
this.id = this.storages.map(s => s.getId()).join("_");
|
||||
}
|
||||
return this.id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a file to all configured storages.
|
||||
* The write operation is considered successful if one of the storage succeed.
|
||||
* @param path - The path where the file should be written.
|
||||
* @param stream - The input stream to be written to storages.
|
||||
* @param options - Write option: chunk number, encryption key, etc ...
|
||||
* @throws Error if the write operation fails for one or more storages.
|
||||
*/
|
||||
write = async (path: string, stream: Stream, options?: WriteOptions): Promise<WriteMetadata> => {
|
||||
logger.debug("Creating streams for all storages ...");
|
||||
const passThroughStreams = this.storages.map(() => new PassThrough());
|
||||
|
||||
// destroy all streams if there is an error in the input stream
|
||||
stream.on("error", err => {
|
||||
logger.error(err, "Error in input stream, destroying all write streams");
|
||||
passThroughStreams.forEach(stream => stream.destroy(err));
|
||||
});
|
||||
|
||||
// Pipe the input stream to each PassThrough stream
|
||||
stream
|
||||
.pipe(new PassThrough())
|
||||
.on("data", chunk => {
|
||||
passThroughStreams.forEach(stream => stream.write(chunk));
|
||||
})
|
||||
.on("end", () => {
|
||||
passThroughStreams.forEach(stream => stream.end());
|
||||
});
|
||||
|
||||
// Write to all storages with error handling
|
||||
const writeResults = await Promise.allSettled(
|
||||
this.storages.map((storage, index) =>
|
||||
storage.write(path, passThroughStreams[index], options),
|
||||
),
|
||||
);
|
||||
|
||||
// Log all errors and throw if all write operations fail
|
||||
const errors = writeResults.filter(result => result.status === "rejected");
|
||||
errors.forEach((error, index) => {
|
||||
const storageId = this.storages[index].getId();
|
||||
logger.error(
|
||||
new OneOfStorageWriteOneFailedException(
|
||||
storageId,
|
||||
`Error writing to storage ${storageId}`,
|
||||
(error as PromiseRejectedResult).reason,
|
||||
),
|
||||
);
|
||||
});
|
||||
if (errors.length === this.storages.length) {
|
||||
throw new WriteFileException(`Write ${path} failed for all storages`);
|
||||
}
|
||||
|
||||
const successResult = writeResults.filter(
|
||||
result => result.status === "fulfilled",
|
||||
)[0] as PromiseFulfilledResult<WriteMetadata>;
|
||||
return successResult.value;
|
||||
};
|
||||
|
||||
/**
|
||||
* Reads a file from the primary storage. If it fails, attempts to read from other storages.
|
||||
* @param path - The path of the file to be read.
|
||||
* @param options
|
||||
* @returns A readable stream of the file.
|
||||
* @throws Error if all storage read attempts fail.
|
||||
*/
|
||||
read = async (path: string, options?: ReadOptions): Promise<Readable> => {
|
||||
for (const storage of this.storages) {
|
||||
try {
|
||||
if (await storage.exists(path, options)) {
|
||||
return await storage.read(path, options);
|
||||
}
|
||||
} catch (err) {
|
||||
logger.error(
|
||||
new OneOfStorageReadOneFailedException(
|
||||
storage.getId(),
|
||||
`Reading ${path} from storage ${storage} failed.`,
|
||||
err,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
throw new FileNotFountException(`Error reading ${path}`);
|
||||
};
|
||||
|
||||
/**
|
||||
* Checks if a file exists in any of the configured storages.
|
||||
* @param path - The path of the file to check.
|
||||
* @param options
|
||||
* @returns A boolean indicating whether the file exists in any storage.
|
||||
*/
|
||||
exists = async (path: string, options?: ReadOptions): Promise<boolean> => {
|
||||
for (const storage of this.storages) {
|
||||
if (await storage.exists(path, options)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Removes a file from all configured storages.
|
||||
* @param path - The path of the file to be removed.
|
||||
* @param options
|
||||
*/
|
||||
remove = async (path: string, options?: DeleteOptions): Promise<boolean> => {
|
||||
return Promise.all(this.storages.map(storage => storage.remove(path, options))).then(array => {
|
||||
return array.reduce((a, b) => a && b);
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Throw when read from one of the storages is filed.
|
||||
*/
|
||||
class StorageException extends Error {
|
||||
constructor(readonly storageId: string, details: string, error: Error) {
|
||||
super(details, error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Throw when read from one of the storages is filed.
|
||||
*/
|
||||
class OneOfStorageReadOneFailedException extends StorageException {}
|
||||
/**
|
||||
* Thrown when write operation to one of the storages is failed.
|
||||
*/
|
||||
class OneOfStorageWriteOneFailedException extends StorageException {}
|
||||
@@ -23,6 +23,12 @@ export type DeleteOptions = {
|
||||
};
|
||||
|
||||
export interface StorageConnectorAPI {
|
||||
/**
|
||||
* Returns identifier of a storage that should've been set in configuration.
|
||||
*
|
||||
*/
|
||||
getId(): string;
|
||||
|
||||
/**
|
||||
* Write a stream to a path
|
||||
*
|
||||
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
TdriveServiceOptions,
|
||||
} from "../../framework";
|
||||
import LocalConnectorService, { LocalConfiguration } from "./connectors/local/service";
|
||||
import S3ConnectorService from "./connectors/S3/s3-service";
|
||||
import S3ConnectorService, { S3Configuration } from "./connectors/S3/s3-service";
|
||||
import StorageAPI, {
|
||||
DeleteOptions,
|
||||
ReadOptions,
|
||||
@@ -18,6 +18,10 @@ import StorageAPI, {
|
||||
WriteOptions,
|
||||
} from "./provider";
|
||||
|
||||
import { OneOfStorageStrategy } from "./oneof-storage-strategy";
|
||||
import { DefaultStorageStrategy } from "./default-storage-strategy";
|
||||
import { FileNotFountException } from "./exceptions";
|
||||
|
||||
type EncryptionConfiguration = {
|
||||
secret: string | null;
|
||||
iv: string | null;
|
||||
@@ -35,26 +39,14 @@ export default class StorageService extends TdriveService<StorageAPI> implements
|
||||
* But for the local storage it's a default value
|
||||
* @private
|
||||
*/
|
||||
private homeDir = "/tdrive";
|
||||
private homeDir = "tdrive";
|
||||
|
||||
constructor(protected options?: TdriveServiceOptions<TdriveServiceConfiguration>) {
|
||||
super(options);
|
||||
const type = this.getConnectorType();
|
||||
if (type === "S3") {
|
||||
logger.info("Using 'S3' connector for storage.");
|
||||
try {
|
||||
this.homeDir = this.configuration.get<string>("S3.homeDirectory");
|
||||
} catch (e) {
|
||||
this.logger.warn("Home directory is not set, using S3.bucket instead");
|
||||
}
|
||||
if (!this.homeDir) {
|
||||
this.homeDir = this.configuration.get<string>("S3.bucket");
|
||||
}
|
||||
if (this.homeDir && this.homeDir.startsWith("/")) {
|
||||
this.logger.error("For S3 connector home directory MUST NOT start with '/'");
|
||||
throw new Error("For S3 connector home directory MUST NOT start with '/'");
|
||||
}
|
||||
}
|
||||
//init home directory variable
|
||||
this.initHomeDirectory();
|
||||
//init connector to storage
|
||||
this.getConnector();
|
||||
}
|
||||
|
||||
api(): StorageAPI {
|
||||
@@ -67,31 +59,7 @@ export default class StorageService extends TdriveService<StorageAPI> implements
|
||||
|
||||
getConnector(): StorageConnectorAPI {
|
||||
if (!this.connector) {
|
||||
const type = this.getConnectorType();
|
||||
if (type === "S3") {
|
||||
logger.info("Using 'S3' connector for storage.");
|
||||
this.connector = new S3ConnectorService({
|
||||
bucket: this.configuration.get<string>("S3.bucket"),
|
||||
region: this.configuration.get<string>("S3.region"),
|
||||
endPoint: this.configuration.get<string>("S3.endPoint"),
|
||||
port: Number(this.configuration.get<number>("S3.port")),
|
||||
useSSL: Boolean(this.configuration.get<boolean>("S3.useSSL")),
|
||||
accessKey: this.configuration.get<string>("S3.accessKey"),
|
||||
secretKey: this.configuration.get<string>("S3.secretKey"),
|
||||
disableRemove: this.configuration.get<boolean>("S3.disableRemove"),
|
||||
});
|
||||
} else {
|
||||
logger.info(
|
||||
`Using 'local' connector for storage${
|
||||
type === "local" ? "" : " (no other connector recognized from configuration type: '%s')"
|
||||
}.`,
|
||||
type,
|
||||
);
|
||||
logger.trace(`Home directory for the storage: ${this.homeDir}`);
|
||||
this.connector = new LocalConnectorService(
|
||||
this.configuration.get<LocalConfiguration>("local"),
|
||||
);
|
||||
}
|
||||
this.connector = this.getStorageStrategy();
|
||||
}
|
||||
return this.connector;
|
||||
}
|
||||
@@ -101,7 +69,8 @@ export default class StorageService extends TdriveService<StorageAPI> implements
|
||||
}
|
||||
|
||||
exists(path: string, options?: ReadOptions): Promise<boolean> {
|
||||
return this.getConnector().exists(path, options);
|
||||
//TODO[ASH] check for all the file chunks
|
||||
return this.getConnector().exists(path + "/chunk1", options);
|
||||
}
|
||||
|
||||
async write(path: string, stream: Stream, options?: WriteOptions): Promise<WriteMetadata> {
|
||||
@@ -134,15 +103,15 @@ export default class StorageService extends TdriveService<StorageAPI> implements
|
||||
}
|
||||
|
||||
async read(path: string, options?: ReadOptions): Promise<Readable> {
|
||||
if (!(await this.exists(path, options))) {
|
||||
throw new FileNotFountException();
|
||||
}
|
||||
try {
|
||||
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
||||
const self = this;
|
||||
const chunks = options?.totalChunks || 1;
|
||||
let count = 1;
|
||||
|
||||
// Check if the first chunk or file exists
|
||||
await self._read(options?.totalChunks ? `${path}/chunk${count}` : path);
|
||||
|
||||
async function factory(callback: (err?: Error, stream?: Stream) => unknown) {
|
||||
if (count > chunks) {
|
||||
callback();
|
||||
@@ -216,4 +185,113 @@ export default class StorageService extends TdriveService<StorageAPI> implements
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiates storage strategy with configuration.
|
||||
* "storage": {
|
||||
* "secret": "",
|
||||
* "iv": "",
|
||||
* "strategy": "oneof",
|
||||
* "type": "local",
|
||||
* "S3": {
|
||||
* "endPoint": "play.min.io",
|
||||
* "port": 9000,
|
||||
* "useSSL": false,
|
||||
* "accessKey": "ABCD",
|
||||
* "secretKey": "x1yz",
|
||||
* "disableRemove": false
|
||||
* },
|
||||
* "local": {
|
||||
* "path": "/tdrive"
|
||||
* },
|
||||
* "oneof": [
|
||||
* {
|
||||
* "type": "S3",
|
||||
* "S3": {}
|
||||
* },
|
||||
* {
|
||||
* "type": "local",
|
||||
* "local": {}
|
||||
* }]
|
||||
* },
|
||||
*/
|
||||
getStorageStrategy(): StorageConnectorAPI {
|
||||
const strategy = this.configuration.get<string>("strategy");
|
||||
if (strategy == "oneof") {
|
||||
logger.info("Creating storage with 'oneof' strategy");
|
||||
const connectors = this.configuration
|
||||
.get<Array<StorageConfiguration>>("oneof")
|
||||
.map(c => this.createConnector(c));
|
||||
return new OneOfStorageStrategy(connectors);
|
||||
} else {
|
||||
logger.info("Creating storage with 'default' strategy");
|
||||
return new DefaultStorageStrategy(this.createConnectorFromConfiguration(this.configuration));
|
||||
}
|
||||
}
|
||||
|
||||
createConnector(config: StorageConfiguration) {
|
||||
const type = config.type;
|
||||
if (type === "S3") {
|
||||
return this.newS3Connector(config.S3);
|
||||
} else {
|
||||
return this.newLocalConnector(config.local, type);
|
||||
}
|
||||
}
|
||||
|
||||
createConnectorFromConfiguration(config: TdriveServiceConfiguration) {
|
||||
const type = config.get<string>("type");
|
||||
if (type === "S3") {
|
||||
return this.newS3Connector(config.get("S3"));
|
||||
} else {
|
||||
return this.newLocalConnector(this.configuration.get<LocalConfiguration>("local"), type);
|
||||
}
|
||||
}
|
||||
|
||||
newS3Connector(config: S3Configuration) {
|
||||
logger.info("Using 'S3' connector for storage.");
|
||||
return new S3ConnectorService(config);
|
||||
}
|
||||
|
||||
newLocalConnector(config: LocalConfiguration, type: string) {
|
||||
logger.info(
|
||||
`Using 'local' connector for storage${
|
||||
type === "local" ? "" : " (no other connector recognized from configuration type: '%s')"
|
||||
}.`,
|
||||
type,
|
||||
);
|
||||
logger.trace(`Home directory for the storage: ${this.homeDir}`);
|
||||
return new LocalConnectorService(config);
|
||||
}
|
||||
|
||||
initHomeDirectory() {
|
||||
const type = this.getConnectorType();
|
||||
if (type === "S3") {
|
||||
logger.info("Using 'S3' connector for storage.");
|
||||
try {
|
||||
this.homeDir = this.configuration.get<string>("S3.homeDirectory");
|
||||
} catch (e) {
|
||||
this.logger.warn("Home directory is not set, using S3.bucket instead");
|
||||
}
|
||||
if (!this.homeDir) {
|
||||
this.homeDir = this.configuration.get<string>("S3.bucket");
|
||||
}
|
||||
if (this.homeDir && this.homeDir.startsWith("/")) {
|
||||
this.logger.error("For S3 connector home directory MUST NOT start with '/'");
|
||||
throw new Error("For S3 connector home directory MUST NOT start with '/'");
|
||||
}
|
||||
} else {
|
||||
this.logger.info("For 'local' connector setting home directory to '/tdrive'");
|
||||
this.homeDir = "/tdrive";
|
||||
}
|
||||
}
|
||||
|
||||
getId() {
|
||||
return this.connector.getId();
|
||||
}
|
||||
}
|
||||
|
||||
type StorageConfiguration = {
|
||||
type: string;
|
||||
S3?: S3Configuration;
|
||||
local?: LocalConfiguration;
|
||||
};
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import Analytics, { IdentifyParams, TrackParams } from "@segment/analytics-node";
|
||||
import { Analytics as AnalyticsAbtract } from "./types";
|
||||
import axios, { AxiosInstance } from "axios";
|
||||
import { logger } from "../../../framework/logger";
|
||||
|
||||
export default class Segment implements AnalyticsAbtract {
|
||||
protected version = "5.0.0"; //Segment analytics lib version
|
||||
@@ -33,7 +34,7 @@ export default class Segment implements AnalyticsAbtract {
|
||||
try {
|
||||
this.analytics.identify(message as IdentifyParams, callback);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
logger.error(err);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,7 +48,7 @@ export default class Segment implements AnalyticsAbtract {
|
||||
try {
|
||||
this.analytics.track(message as TrackParams, callback);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
logger.error(err);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { FastifyInstance } from "fastify";
|
||||
import { logger } from "../../framework/logger";
|
||||
|
||||
function serverErrorHandler(server: FastifyInstance): void {
|
||||
server.setErrorHandler(async (err, request, reply) => {
|
||||
console.error(`Got ${reply.statusCode} error on request ${request.id} : `, err);
|
||||
logger.error(`Got ${reply.statusCode} error on request ${request.id} : `, err);
|
||||
server.log.debug(`Got ${reply.statusCode} error on request ${request.id} : ${err.toString()}`);
|
||||
await reply.send(
|
||||
reply.statusCode == 500
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Initializable, TdriveServiceProvider } from "../../../core/platform/framework";
|
||||
import { Initializable, logger, TdriveServiceProvider } from "../../../core/platform/framework";
|
||||
import { ListResult, Paginable } from "../../../core/platform/framework/api/crud-service";
|
||||
import gr from "../../global-resolver";
|
||||
import {
|
||||
@@ -39,7 +39,7 @@ export class CompanyApplicationServiceImpl implements TdriveServiceProvider, Ini
|
||||
app.application.api = null;
|
||||
return app;
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
logger.error(err);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -285,7 +285,7 @@ export const getAccessLevel = async (
|
||||
);
|
||||
if (matchingChannel) return matchingChannel.level;
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
logger.info(e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -406,7 +406,7 @@ export const getSharedByUser = (
|
||||
return getGrantorAndThrowIfEmpty(entity);
|
||||
}
|
||||
if (entity.type === "company" && entity.id === context.company.id && entity.level != "none") {
|
||||
console.log("company", entity);
|
||||
logger.info("company", entity);
|
||||
return getGrantorAndThrowIfEmpty(entity);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@ export class DocumentsProcessor
|
||||
);
|
||||
content_keywords = await getKeywordsOfFile(storedFile.mime, storedFile.name, storedFile.file);
|
||||
} catch (error) {
|
||||
console.debug(error);
|
||||
logger.debug(error);
|
||||
logger.error({ error: `${error}` }, "Failed to generate content keywords");
|
||||
}
|
||||
return { content_keywords, item: message.item };
|
||||
|
||||
@@ -470,7 +470,7 @@ export class DocumentsService {
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("🚀🚀 error:", error);
|
||||
logger.error(error, "🚀🚀 error:");
|
||||
}
|
||||
|
||||
await this.repository.save(driveItem);
|
||||
@@ -557,6 +557,16 @@ export class DocumentsService {
|
||||
throw Error("Move operation not permitted");
|
||||
} else {
|
||||
oldParent = item.parent_id;
|
||||
const newParentId = content.parent_id;
|
||||
const needRenameTo = await getItemName(
|
||||
newParentId,
|
||||
item.id,
|
||||
item.name,
|
||||
item.is_directory,
|
||||
this.repository,
|
||||
context,
|
||||
);
|
||||
if (needRenameTo !== item.name) renamedTo = item.name = needRenameTo;
|
||||
}
|
||||
if (key === "access_info") {
|
||||
// if manage access is disabled, we don't allow changing access level
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { FastifyInstance, FastifyPluginCallback } from "fastify";
|
||||
import { DocumentsController } from "./controllers";
|
||||
import { createDocumentSchema, createVersionSchema, beginEditingSchema } from "./schemas";
|
||||
// import profilerPlugin from "../../../utils/profiler";
|
||||
|
||||
const baseUrl = "/companies/:company_id";
|
||||
const serviceUrl = `${baseUrl}/item`;
|
||||
@@ -10,11 +9,6 @@ const editingSessionBase = "/editing_session/:editing_session_key";
|
||||
const routes: FastifyPluginCallback = (fastify: FastifyInstance, _options, next) => {
|
||||
const documentsController = new DocumentsController();
|
||||
|
||||
// fastify.register(profilerPlugin, {
|
||||
// active: documentsController.profilingEnabled,
|
||||
// outputDir: "profiles",
|
||||
// });
|
||||
|
||||
fastify.route({
|
||||
method: "GET",
|
||||
url: `${serviceUrl}`,
|
||||
|
||||
@@ -431,7 +431,7 @@ export class FileServiceImpl {
|
||||
id: externalId,
|
||||
company_id: "00000000-0000-4000-0000-000000000000",
|
||||
});
|
||||
const exist = await gr.platformServices.storage.exists(getFilePath(file) + "/chunk1");
|
||||
const exist = await gr.platformServices.storage.exists(getFilePath(file));
|
||||
if (exist) {
|
||||
return { exist: true, file };
|
||||
} else {
|
||||
|
||||
@@ -6,6 +6,7 @@ import { UploadOptions } from "../../types";
|
||||
import { PublicFile } from "../../entities/file";
|
||||
import gr from "../../../global-resolver";
|
||||
import { formatAttachmentContentDispositionHeader } from "../../utils";
|
||||
import { logger } from "../../../../core/platform/framework";
|
||||
|
||||
export class FileController {
|
||||
async save(
|
||||
@@ -54,7 +55,7 @@ export class FileController {
|
||||
response.type(data.mime);
|
||||
return response.send(data.file);
|
||||
} catch (e) {
|
||||
console.log("!!!" + e);
|
||||
logger.info(e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
@@ -80,7 +81,7 @@ export class FileController {
|
||||
response.send(data.file);
|
||||
return response;
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
logger.error(err);
|
||||
response.statusCode = 500;
|
||||
response.send("");
|
||||
}
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import { fromPath } from "pdf2pic";
|
||||
import { mkdirSync } from "fs";
|
||||
import { cleanFiles, getTmpFile } from "../../../../../utils/files";
|
||||
import { logger } from "../../../../../core/platform/framework";
|
||||
|
||||
class PDFConversionError extends Error {}
|
||||
|
||||
export async function convertFromPdf(
|
||||
inputPath: string,
|
||||
@@ -33,11 +36,14 @@ export async function convertFromPdf(
|
||||
//Just no more page to convert
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
const pdfConversionError = new PDFConversionError("Can't convert file with pdf-image.", {
|
||||
cause: error,
|
||||
});
|
||||
logger.error(pdfConversionError);
|
||||
for (const file of pages) {
|
||||
cleanFiles([file]);
|
||||
}
|
||||
throw Error("Can't convert file with pdf-image.");
|
||||
throw pdfConversionError;
|
||||
}
|
||||
return { output: pages, done: true };
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
CompanyStatsObject,
|
||||
CompanyUserObject,
|
||||
} from "./web/types";
|
||||
import { logger } from "../../core/platform/framework";
|
||||
|
||||
export function formatCompany(
|
||||
companyEntity: Company,
|
||||
@@ -75,6 +76,8 @@ export function formatCompany(
|
||||
},
|
||||
);
|
||||
|
||||
logger.info("🚀🚀 res.plan.features: ", res.plan.features);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
@@ -490,7 +490,7 @@ export class WorkspaceServiceImpl implements TdriveServiceProvider, Initializabl
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
logger.warn(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,6 +35,7 @@ import WorkspacePendingUser from "../../entities/workspace_pending_users";
|
||||
import { ConsoleCompany, CreateConsoleUser } from "../../../console/types";
|
||||
import { hasCompanyAdminLevel } from "../../../../utils/company";
|
||||
import gr from "../../../global-resolver";
|
||||
import { logger } from "../../../../core/platform/framework";
|
||||
|
||||
export class WorkspaceUsersCrudController
|
||||
implements
|
||||
@@ -525,7 +526,7 @@ export class WorkspaceUsersCrudController
|
||||
|
||||
return { status: "success" };
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
logger.warn(e);
|
||||
return {
|
||||
status: "error",
|
||||
};
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { mkdirSync, existsSync, promises as fsPromise, createWriteStream, readFileSync } from "fs";
|
||||
import { Readable } from "stream";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
import { logger } from "../core/platform/framework";
|
||||
|
||||
const { unlink } = fsPromise;
|
||||
|
||||
@@ -50,7 +51,7 @@ export const writeToTemporaryFile = async (input: Readable, extension: string):
|
||||
|
||||
return temporaryFilePath;
|
||||
} catch (error) {
|
||||
console.debug(error);
|
||||
logger.debug(error);
|
||||
|
||||
throw Error(error);
|
||||
}
|
||||
|
||||
@@ -1,61 +0,0 @@
|
||||
import fs from "fs";
|
||||
import fp from "fastify-plugin";
|
||||
import v8profiler from "v8-profiler-next";
|
||||
|
||||
export class Profiler {
|
||||
title: string;
|
||||
active: boolean;
|
||||
outputDir: string;
|
||||
constructor(options) {
|
||||
const { title, active, outputDir } = options;
|
||||
this.title = title;
|
||||
this.active = active;
|
||||
this.outputDir = outputDir;
|
||||
}
|
||||
|
||||
start() {
|
||||
if (this.active) {
|
||||
v8profiler.startProfiling(this.title, true);
|
||||
}
|
||||
}
|
||||
|
||||
finish() {
|
||||
if (this.active) {
|
||||
const profile = v8profiler.stopProfiling(this.title);
|
||||
if (profile === undefined) {
|
||||
console.log("profile is undefined: ", this.title);
|
||||
return;
|
||||
}
|
||||
profile.export((error, result) => {
|
||||
if (error) {
|
||||
console.log("Profiling error: ", error);
|
||||
} else {
|
||||
fs.writeFileSync(`${process.cwd()}/${this.outputDir}/${this.title}.cpuprofile`, result);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function profilerPlugin(fastify, options) {
|
||||
fastify.addHook("onRequest", async request => {
|
||||
const profiler = new Profiler({
|
||||
title: `${request.method}-${request.url}`,
|
||||
active: options.active,
|
||||
outputDir: options.outputDir || "profiles",
|
||||
});
|
||||
|
||||
profiler.start();
|
||||
|
||||
// Attach profiler to request for access in routes or other hooks
|
||||
request.profiler = profiler;
|
||||
});
|
||||
|
||||
fastify.addHook("onResponse", async request => {
|
||||
if (request.profiler) {
|
||||
request.profiler.finish();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export default fp(profilerPlugin);
|
||||
Reference in New Issue
Block a user