file storage workspace id prefix (#6230)
closes https://github.com/twentyhq/twenty/issues/6155 just an idea, i guess this could work well, but im open for discussion --------- Co-authored-by: Weiko <corentin@twenty.com>
This commit is contained in:
@ -1,6 +1,7 @@
|
||||
import { QueryResultGettersFactory } from './query-result-getters.factory';
|
||||
import { RecordPositionFactory } from './record-position.factory';
|
||||
import { QueryRunnerArgsFactory } from './query-runner-args.factory';
|
||||
import { RecordPositionFactory } from './record-position.factory';
|
||||
|
||||
import { QueryResultGettersFactory } from './query-result-getters/query-result-getters.factory';
|
||||
|
||||
export const workspaceQueryRunnerFactories = [
|
||||
QueryRunnerArgsFactory,
|
||||
|
||||
@ -1,75 +0,0 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { addMilliseconds } from 'date-fns';
|
||||
import ms from 'ms';
|
||||
|
||||
import { ObjectMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/object-metadata.interface';
|
||||
|
||||
import { EnvironmentService } from 'src/engine/integrations/environment/environment.service';
|
||||
import { TokenService } from 'src/engine/core-modules/auth/services/token.service';
|
||||
|
||||
@Injectable()
|
||||
export class QueryResultGettersFactory {
|
||||
constructor(
|
||||
private readonly tokenService: TokenService,
|
||||
private readonly environmentService: EnvironmentService,
|
||||
) {}
|
||||
|
||||
async create<Result>(
|
||||
result: Result,
|
||||
objectMetadataItem: ObjectMetadataInterface,
|
||||
): Promise<Result> {
|
||||
// TODO: look for file type once implemented
|
||||
switch (objectMetadataItem.nameSingular) {
|
||||
case 'attachment':
|
||||
return this.applyAttachmentGetters(result);
|
||||
default:
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
private async applyAttachmentGetters<Result>(
|
||||
attachments: any,
|
||||
): Promise<Result> {
|
||||
if (!attachments || !attachments.edges) {
|
||||
return attachments;
|
||||
}
|
||||
|
||||
const fileTokenExpiresIn = this.environmentService.get(
|
||||
'FILE_TOKEN_EXPIRES_IN',
|
||||
);
|
||||
const secret = this.environmentService.get('FILE_TOKEN_SECRET');
|
||||
|
||||
const mappedEdges = await Promise.all(
|
||||
attachments.edges.map(async (attachment: any) => {
|
||||
if (!attachment.node.id || !attachment?.node?.fullPath) {
|
||||
return attachment;
|
||||
}
|
||||
|
||||
const expirationDate = addMilliseconds(
|
||||
new Date(),
|
||||
ms(fileTokenExpiresIn),
|
||||
);
|
||||
|
||||
const signedPayload = await this.tokenService.encodePayload(
|
||||
{
|
||||
expiration_date: expirationDate,
|
||||
attachment_id: attachment.node.id,
|
||||
},
|
||||
{
|
||||
secret,
|
||||
},
|
||||
);
|
||||
|
||||
attachment.node.fullPath = `${attachment.node.fullPath}?token=${signedPayload}`;
|
||||
|
||||
return attachment;
|
||||
}),
|
||||
);
|
||||
|
||||
return {
|
||||
...attachments,
|
||||
edges: mappedEdges,
|
||||
} as Result;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,45 @@
|
||||
import { addMilliseconds } from 'date-fns';
|
||||
import ms from 'ms';
|
||||
|
||||
import { QueryResultGuetterHandlerInterface } from 'src/engine/api/graphql/workspace-query-runner/factories/query-result-getters/interfaces/query-result-getter-handler.interface';
|
||||
|
||||
import { TokenService } from 'src/engine/core-modules/auth/services/token.service';
|
||||
import { EnvironmentService } from 'src/engine/integrations/environment/environment.service';
|
||||
|
||||
export class AttachmentQueryResultGetterHandler
|
||||
implements QueryResultGuetterHandlerInterface
|
||||
{
|
||||
constructor(
|
||||
private readonly tokenService: TokenService,
|
||||
private readonly environmentService: EnvironmentService,
|
||||
) {}
|
||||
|
||||
async process(attachment: any, workspaceId: string): Promise<any> {
|
||||
if (!attachment.id || !attachment?.fullPath) {
|
||||
return attachment;
|
||||
}
|
||||
|
||||
const fileTokenExpiresIn = this.environmentService.get(
|
||||
'FILE_TOKEN_EXPIRES_IN',
|
||||
);
|
||||
const secret = this.environmentService.get('FILE_TOKEN_SECRET');
|
||||
|
||||
const expirationDate = addMilliseconds(new Date(), ms(fileTokenExpiresIn));
|
||||
|
||||
const signedPayload = await this.tokenService.encodePayload(
|
||||
{
|
||||
expiration_date: expirationDate,
|
||||
attachment_id: attachment.id,
|
||||
workspace_id: workspaceId,
|
||||
},
|
||||
{
|
||||
secret,
|
||||
},
|
||||
);
|
||||
|
||||
return {
|
||||
...attachment,
|
||||
fullPath: `${attachment.fullPath}?token=${signedPayload}`,
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,3 @@
|
||||
export interface QueryResultGuetterHandlerInterface {
|
||||
process(result: any, workspaceId: string): Promise<any>;
|
||||
}
|
||||
@ -0,0 +1,73 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { QueryResultGuetterHandlerInterface } from 'src/engine/api/graphql/workspace-query-runner/factories/query-result-getters/interfaces/query-result-getter-handler.interface';
|
||||
import { ObjectMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/object-metadata.interface';
|
||||
|
||||
import { AttachmentQueryResultGetterHandler } from 'src/engine/api/graphql/workspace-query-runner/factories/query-result-getters/handlers/attachment-query-result-getter.handler';
|
||||
import { TokenService } from 'src/engine/core-modules/auth/services/token.service';
|
||||
import { EnvironmentService } from 'src/engine/integrations/environment/environment.service';
|
||||
|
||||
@Injectable()
|
||||
export class QueryResultGettersFactory {
|
||||
private handlers: Map<string, QueryResultGuetterHandlerInterface>;
|
||||
|
||||
constructor(
|
||||
private readonly tokenService: TokenService,
|
||||
private readonly environmentService: EnvironmentService,
|
||||
) {
|
||||
this.initializeHandlers();
|
||||
}
|
||||
|
||||
private initializeHandlers() {
|
||||
this.handlers = new Map<string, QueryResultGuetterHandlerInterface>([
|
||||
[
|
||||
'attachment',
|
||||
new AttachmentQueryResultGetterHandler(
|
||||
this.tokenService,
|
||||
this.environmentService,
|
||||
),
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
async create(
|
||||
result: any,
|
||||
objectMetadataItem: ObjectMetadataInterface,
|
||||
workspaceId: string,
|
||||
): Promise<any> {
|
||||
const handler = this.getHandler(objectMetadataItem.nameSingular);
|
||||
|
||||
if (result.edges) {
|
||||
return {
|
||||
...result,
|
||||
edges: await Promise.all(
|
||||
result.edges.map(async (edge: any) => ({
|
||||
...edge,
|
||||
node: await handler.process(edge.node, workspaceId),
|
||||
})),
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
if (result.records) {
|
||||
return {
|
||||
...result,
|
||||
records: await Promise.all(
|
||||
result.records.map(
|
||||
async (item: any) => await handler.process(item, workspaceId),
|
||||
),
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
return await handler.process(result, workspaceId);
|
||||
}
|
||||
|
||||
private getHandler(objectType: string): QueryResultGuetterHandlerInterface {
|
||||
return (
|
||||
this.handlers.get(objectType) || {
|
||||
process: (result: any) => result,
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -25,7 +25,7 @@ import {
|
||||
import { ObjectMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/object-metadata.interface';
|
||||
|
||||
import { WorkspaceQueryBuilderFactory } from 'src/engine/api/graphql/workspace-query-builder/workspace-query-builder.factory';
|
||||
import { QueryResultGettersFactory } from 'src/engine/api/graphql/workspace-query-runner/factories/query-result-getters.factory';
|
||||
import { QueryResultGettersFactory } from 'src/engine/api/graphql/workspace-query-runner/factories/query-result-getters/query-result-getters.factory';
|
||||
import { QueryRunnerArgsFactory } from 'src/engine/api/graphql/workspace-query-runner/factories/query-runner-args.factory';
|
||||
import {
|
||||
CallWebhookJobsJob,
|
||||
@ -125,6 +125,7 @@ export class WorkspaceQueryRunnerService {
|
||||
result,
|
||||
objectMetadataItem,
|
||||
'',
|
||||
workspaceId,
|
||||
);
|
||||
}
|
||||
|
||||
@ -167,6 +168,7 @@ export class WorkspaceQueryRunnerService {
|
||||
result,
|
||||
objectMetadataItem,
|
||||
'',
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
return parsedResult?.edges?.[0]?.node;
|
||||
@ -235,6 +237,7 @@ export class WorkspaceQueryRunnerService {
|
||||
result,
|
||||
objectMetadataItem,
|
||||
'',
|
||||
workspaceId,
|
||||
true,
|
||||
);
|
||||
}
|
||||
@ -283,6 +286,7 @@ export class WorkspaceQueryRunnerService {
|
||||
result,
|
||||
objectMetadataItem,
|
||||
'insertInto',
|
||||
workspaceId,
|
||||
)
|
||||
)?.records;
|
||||
|
||||
@ -418,6 +422,7 @@ export class WorkspaceQueryRunnerService {
|
||||
result,
|
||||
objectMetadataItem,
|
||||
'update',
|
||||
workspaceId,
|
||||
)
|
||||
)?.records;
|
||||
|
||||
@ -485,6 +490,7 @@ export class WorkspaceQueryRunnerService {
|
||||
result,
|
||||
objectMetadataItem,
|
||||
'update',
|
||||
workspaceId,
|
||||
)
|
||||
)?.records;
|
||||
|
||||
@ -555,6 +561,7 @@ export class WorkspaceQueryRunnerService {
|
||||
result,
|
||||
objectMetadataItem,
|
||||
'deleteFrom',
|
||||
workspaceId,
|
||||
)
|
||||
)?.records;
|
||||
|
||||
@ -618,6 +625,7 @@ export class WorkspaceQueryRunnerService {
|
||||
result,
|
||||
objectMetadataItem,
|
||||
'deleteFrom',
|
||||
workspaceId,
|
||||
)
|
||||
)?.records;
|
||||
|
||||
@ -721,6 +729,7 @@ export class WorkspaceQueryRunnerService {
|
||||
graphqlResult: PGGraphQLResult | undefined,
|
||||
objectMetadataItem: ObjectMetadataInterface,
|
||||
command: string,
|
||||
workspaceId: string,
|
||||
isMultiQuery = false,
|
||||
): Promise<Result> {
|
||||
const entityKey = `${command}${computeObjectTargetTable(
|
||||
@ -767,6 +776,7 @@ export class WorkspaceQueryRunnerService {
|
||||
const resultWithGetters = await this.queryResultGettersFactory.create(
|
||||
result,
|
||||
objectMetadataItem,
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
return parseResult(resultWithGetters);
|
||||
@ -780,7 +790,7 @@ export class WorkspaceQueryRunnerService {
|
||||
): Promise<Result> {
|
||||
const result = await this.execute(query, workspaceId);
|
||||
|
||||
return this.parseResult(result, objectMetadataItem, command);
|
||||
return this.parseResult(result, objectMetadataItem, command, workspaceId);
|
||||
}
|
||||
|
||||
async triggerWebhooks<Record>(
|
||||
|
||||
Reference in New Issue
Block a user