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:
rostaklein
2024-08-01 18:07:22 +02:00
committed by GitHub
parent 5c92ab937e
commit a424c63476
26 changed files with 727 additions and 231 deletions

View File

@ -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,

View File

@ -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;
}
}

View File

@ -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}`,
};
}
}

View File

@ -0,0 +1,3 @@
export interface QueryResultGuetterHandlerInterface {
process(result: any, workspaceId: string): Promise<any>;
}

View File

@ -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,
}
);
}
}

View File

@ -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>(